Every function in <string.h> is a small loop over a char array that stops at the null terminator. Once that is clear, the library stops looking like a list of cryptic names and starts looking like the handful of loops you would have written yourself. This page covers the ones that carry almost all real work - and the sizing rules that keep them from writing past the end of your buffers.
Everything here needs one include:
#include <string.h>
strlen: How Many Characters
strlen counts bytes up to but not including the terminator. It returns size_t, so print it with %zu.
strlen is 6; sizeof is 32. The first is the text, the second is the storage - see strings for why that distinction matters so much.
strlen walks the whole string each call, so do not put it in a loop condition over the same unchanging string:
/* re-counts the whole string on every iteration */
for (size_t i = 0; i < strlen(s); i++) { ... }
/* count once */
size_t n = strlen(s);
for (size_t i = 0; i < n; i++) { ... }
strcpy and strncpy: Copying
strcpy(dst, src) copies the characters plus the terminator. It has no idea how large dst is, so the caller must guarantee dst holds at least strlen(src) + 1 bytes.
strncpy takes a maximum count, but it has a famous trap: when the source is at least n bytes long it copies exactly n characters and no terminator. Always terminate yourself:
The sizeof dst - 1 plus explicit terminator is the pattern to memorize. (strncpy also pads short sources with zeros out to n bytes, which is wasted work for large buffers - it was designed for fixed-width records, not for safety.)
strcat and strncat: Joining
strcat(dst, src) appends src to the end of whatever is already in dst. The destination must hold both strings plus one terminator, and it must already contain a valid string - appending to uninitialized memory is undefined behavior.
Note char path[64] = "/home/ada"; rather than char path[64]; - the initializer is what makes it a valid string for the first strcat.
strncat(dst, src, n) appends at most n characters and always adds a terminator, so n is the space remaining, not the total buffer size:
strncat(dst, src, sizeof dst - strlen(dst) - 1);
Repeated strcat calls re-scan the destination to find its end every time. Building a long string in a loop that way is quadratic; for anything sizeable, track your own write position or use snprintf, covered in string conversion.
strcmp: Comparing
== compares addresses, so it is false for two distinct arrays holding the same text. strcmp compares the characters and returns the sign of the difference.
Three rules:
- Test
strcmp(a, b) == 0for equality. Writingif (strcmp(a, b))means "if they differ", which reads backwards to almost everyone. - Only the sign is defined. Do not compare the result against
1or-1. - The ordering is by byte value, so
"Zebra"sorts before"apple"in ASCII. For case-insensitive comparison, lowercase both copies first - the widely seenstrcasecmpis a POSIX extension, not standard C.
strncmp(a, b, n) compares only the first n characters, which is the clean way to test a prefix:
strchr and strstr: Searching
strchr(s, ch) finds the first occurrence of a character; strrchr finds the last. strstr(haystack, needle) finds a substring. All three return a pointer into the original string, or NULL when there is no match.
Subtracting the returned pointer from the start gives the index; its type is ptrdiff_t, printed with %td. Because the result points inside the original array, at + 1 is the rest of the string with no copying at all - a very common C idiom.
Always check for NULL before dereferencing. strchr(email, '@') + 1 on a string with no @ computes an address from NULL and the program is no longer defined.
Splitting a String
There is no split function, but strchr plus a terminator does the job in place. This example cuts an email into user and domain:
memcpy copies an exact byte count without caring about terminators, which is what you want when you already know the length. The terminator is then written by hand.
A Worked Example: Normalizing a Name
Putting the tour together - trim, compare, copy, and join:
Every write is bounded by the destination's own sizeof, and every strncpy is followed by an explicit terminator. That discipline, applied consistently, is what makes C string code safe.
Sizing Rules to Keep
strcpyneedsstrlen(src) + 1bytes free in the destination.strcatneedsstrlen(dst) + strlen(src) + 1.- After
strncpy, writedst[n - 1] = '\0'yourself. - For
strncat, the count is the space left over:sizeof dst - strlen(dst) - 1. - Check
strchrandstrstrforNULLbefore using the result. - Use
sizeof dstonly whendstis a real array. Inside a function takingchar *dst,sizeofgives the pointer's size - pass the buffer length as a separate parameter.
Frequently Asked Questions
What does strcmp return in C?
Zero when the two strings are identical, a negative value when the first sorts before the second, and a positive value when it sorts after. Only the sign is meaningful - never assume it returns -1 or 1. The test you want is almost always if (strcmp(a, b) == 0).
Why can't I compare strings with == in C?
a == b compares the two addresses, not the characters. Two separate arrays holding "cat" live at different addresses, so the comparison is false even though the text matches. Use strcmp(a, b) == 0 from <string.h>.
What is the difference between strcpy and strncpy?
strcpy copies until the source's '\0' with no idea how big the destination is. strncpy stops after at most n bytes - but if the source is that long it copies no terminator, so you must write dst[n - 1] = '\0'; yourself. Neither is automatically safe; the size discipline is yours.
How do I find a substring in C?
strstr(haystack, needle) returns a pointer to the first occurrence, or NULL if there is none. Because it returns a pointer into the original string, found - haystack gives you the index. Use strchr when you are looking for a single character.