The string.h functions
Lesson 23 wrote the loops for length, copy and comparison by hand. The functions in string.h do the same work, are tested on every platform, and have names every C programmer recognises. Their contract is the terminated array from that lesson: a function that receives a pointer without a zero somewhere after it will read past the end.
strlen and strnlen
strlen returns the number of bytes before the terminator, and strnlen stops after a given maximum. The second is the safe choice for untrusted input, because it cannot walk past a buffer of known size. Both take the address of the first byte, which an array name provides.
#include <stdio.h>
#include <string.h>
int main(void)
{
char text[] = "programming";
printf("strlen: %zu\n", strlen(text));
printf("strnlen 4: %zu\n", strnlen(text, 4));
printf("sizeof: %zu\n", sizeof text);
return 0;
}
gcc -Wall -Wextra lengths.c -o lengths
./lengths
strlen: 11
strnlen 4: 4
sizeof: 12
The three numbers differ for good reasons: eleven visible characters, a cap of four applied to the same string, and twelve bytes of storage including the terminator. Returning size_t makes the results unsigned, so print them with %zu as lesson 6 shows.
strcpy, strncpy and strcat
strcpy copies the whole source including the terminator, and strcat appends at the current end of the destination. Neither takes a size, so the destination must be large enough for the result. strncpy takes a limit but does not guarantee a terminator, which the example handles explicitly.
#include <stdio.h>
#include <string.h>
int main(void)
{
char source[] = "hello";
char dest[16];
char small[8];
strcpy(dest, source);
strcat(dest, ", world");
printf("%s (%zu)\n", dest, strlen(dest));
strncpy(small, "abcdefghij", sizeof small - 1);
small[sizeof small - 1] = '\0';
printf("%s\n", small);
return 0;
}
gcc -Wall -Wextra copy.c -o copy
./copy
hello, world (12)
abcdefg
strncpy stopped after seven bytes and the program wrote the zero into the last byte itself. Passing sizeof small and forgetting the terminator leaves the destination full and the string running past its end.
strcmp and strncmp
strcmp compares byte by byte and returns a negative number, zero or a positive number; only the sign is specified, so compare the result with zero instead of with -1. The strncmp form limits the comparison to a prefix, which is how a filter matches the start of a path.
#include <stdio.h>
#include <string.h>
int main(void)
{
char a[] = "apple";
char b[] = "apricot";
char c[] = "apple";
printf("apple before apricot: %d\n", strcmp(a, b) < 0);
printf("apple equals apple: %d\n", strcmp(a, c) == 0);
printf("apricot after apple: %d\n", strcmp(b, a) > 0);
printf("first two letters equal: %d\n", strncmp(a, b, 2) == 0);
return 0;
}
gcc -Wall -Wextra compare.c -o compare
./compare
apple before apricot: 1
apple equals apple: 1
apricot after apple: 1
first two letters equal: 1
Every line prints 1 because every statement is true. Writing if (strcmp(a, b)) without the comparison is a common slip: the condition is false exactly when the strings are equal, the opposite of what the sentence reads like.
strchr, strstr and strspn
strchr finds the first occurrence of a character and strstr the first occurrence of a substring; both return a pointer to that place, so printing the result with %s shows the rest of the string from there. strspn counts the initial characters that belong to a set.
#include <stdio.h>
#include <string.h>
int main(void)
{
char text[] = "the world of words";
printf("from w: %s\n", strchr(text, 'w'));
printf("substring: %s\n", strstr(text, "world"));
printf("prefix run: %zu\n", strspn("aaabbb", "ab"));
printf("stops at c: %zu\n", strspn("aaacbb", "ab"));
return 0;
}
gcc -Wall -Wextra search.c -o search
./search
from w: world of words
substring: world of words
prefix run: 6
stops at c: 3
A search that finds nothing returns NULL, and printing that with %s is undefined behaviour. Check the result before using it.
memcpy and memset
memcpy and memset work on bytes, not on characters, and take a count that is the size of the region. memset writes the same byte everywhere, which is how a buffer is wiped or filled before a copy. Overlapping regions need memmove instead of memcpy.
#include <stdio.h>
#include <string.h>
int main(void)
{
char buffer[8];
char copy[8];
memset(buffer, 'x', 5);
buffer[5] = '\0';
memcpy(copy, buffer, 6);
printf("%s %zu\n", copy, strlen(copy));
return 0;
}
gcc -Wall -Wextra bytes.c -o bytes
./bytes
xxxxx 5
The copy includes the terminator, which is why the six bytes are enough and strlen still reports five. Forgetting the extra byte for the zero is the same mistake as in strncpy, just spelled with a number.
An overflow and an array comparison
A destination of four bytes cannot hold five characters and a terminator. At -O2 the compiler sees both sizes and reports the write before it happens.
#include <stdio.h>
#include <string.h>
int main(void)
{
char small[4];
memcpy(small, "hello", 6);
printf("%s\n", small);
return 0;
}
gcc -O2 -Wall -Wextra overflow.c -o overflow
In file included from overflow.c:2:
In function 'memcpy',
inlined from 'main' at overflow.c:8:5:
/usr/include/fortify/string.h:52:31: warning: array subscript 6 is outside array bounds of 'char[4]' [-Warray-bounds=]
52 | if ((__d < __s && __d + __n > __s) ||
| ~~~~^~~~~
overflow.c: In function 'main':
overflow.c:6:10: note: at offset 6 into object 'small' of size 4
6 | char small[4];
| ^~~~~
The second mistake is comparing two arrays with ==. That compares addresses, not contents, and the compiler points out that the comparison is probably not what was meant. The program below prints not equal although both arrays contain "same".
#include <stdio.h>
int main(void)
{
char a[] = "same";
char b[] = "same";
if (a == b) {
printf("equal\n");
} else {
printf("not equal\n");
}
return 0;
}
gcc -Wall -Wextra arraycmp.c -o arraycmp
./arraycmp
arraycmp.c: In function 'main':
arraycmp.c:8:11: warning: comparison between two arrays [-Warray-compare]
8 | if (a == b) {
| ^~
arraycmp.c:8:11: note: use '&a[0] == &b[0]' to compare the addresses
not equal
The warning already gives the correct reading: the addresses differ because the two arrays are different objects. Use strcmp when the contents are what matters, and the array comparison only when the identity of the array is the question.
Exercises
- Read a word into a buffer of eight bytes with fgets and print it with its strlen and its sizeof.
- Copy the first three characters of a long string with strncpy, terminate the result, and print it.
- Find the last space of a sentence with strrchr and print the word that follows it. What does the function return when there is no space?
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does
strlencount?Answer
The bytes before the terminator
strlenwalks to the zero and returns the number of bytes before it, so the terminator is not counted. -
What is the danger of
strncpycompared withstrcpy?Answer
It may leave the destination unterminated
strncpywrites at most the given number of bytes and does not add a zero when the source fills the limit, so the caller terminates it. -
What is guaranteed about the value returned by
strcmp?Answer
Only its sign
The standard specifies only negative, zero or positive, so comparisons use
< 0,== 0or> 0rather than exact values. -
Which function is safe for two overlapping byte regions?
Answer
memmovememcpyrequires non-overlapping regions, whilememmovehandles a copy whose source and destination overlap. -
What does this program print?
#include <stdio.h> #include <string.h> int main(void) { char text[] = "programming"; printf("%zu\n", strlen(text)); return 0; }Answer
11
The word has eleven visible characters, and
strlendoes not count the terminator. -
What does this program print?
#include <stdio.h> int main(void) { char text[] = "programming"; printf("%zu\n", sizeof text); return 0; }Answer
12
The array holds eleven characters plus the terminating zero, so its size is twelve bytes.
-
What does this program print?
#include <stdio.h> #include <string.h> int main(void) { char small[8]; strncpy(small, "abcdefghij", sizeof small - 1); small[sizeof small - 1] = '\0'; printf("%s\n", small); return 0; }Answer
abcdefg
The call copies seven bytes, and the program writes the terminator into the last byte itself.
-
What does this program print?
#include <stdio.h> #include <string.h> int main(void) { printf("%zu\n", strspn("aaabbb", "ab")); return 0; }Answer
6
Every character of the string belongs to the set, so the initial run is the whole string, six characters.
-
This program is built with
gcc -std=c17 -O2 -Wall -Wextra overflow.c -o overflow. Which warning flag does GCC print?#include <stdio.h> #include <string.h> int main(void) { char small[4]; memcpy(small, "hello", 6); printf("%s\n", small); return 0; }Answer
-Warray-bounds=
The six-byte copy past a four-byte object is reported by
-Warray-bounds=. -
What does this program print?
#include <stdio.h> int main(void) { char a[] = "same"; char b[] = "same"; if (a == b) printf("equal\n"); else printf("not equal\n"); return 0; }Answer
not equal
The array names decay to pointers, so the comparison asks whether the arrays are the same object; they are not.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev