String Functions
<string.h> provides C's toolkit for copying, joining, comparing and searching strings. Several of the classic functions don't check sizes, so this lesson shows each one alongside the safe way to use it.
Overview
| Function | Does |
|---|---|
strlen(s) | Length, not counting '\0' |
strcmp(a, b), strncmp(a, b, n) | Compare: <0, 0 or >0 |
strcpy(dst, src), strncpy | Copy (no size check / awkward semantics) |
strcat(dst, src), strncat | Append |
snprintf(dst, size, fmt, …) | Safe copying, joining and formatting |
strchr(s, c), strrchr | First / last occurrence of a character |
strstr(s, sub) | First occurrence of a substring |
strspn, strcspn | Length of the start made of (or free of) given characters |
strtok(s, delims) | Split into tokens |
memcpy, memmove, memset, memcmp | Work on raw bytes, by count |
Comparing
strcmp returns 0 when the strings are equal, a negative number if the first sorts before the second, and a positive number if after:
#include <stdio.h> #include <string.h> int main(void) { printf("%d\n", strcmp("apple", "apple") == 0); printf("%d\n", strcmp("apple", "banana") < 0); printf("%d\n", strcmp("Zebra", "apple") < 0); // uppercase sorts before lowercase printf("%d\n", strncmp("report-2026.txt", "report", 6) == 0); return 0; }
1 1 1 1
Only test the sign (or zero) — the actual numbers differ between systems. Comparison is by character code, so 'Z' (90) comes before 'a' (97).
Copying and joining safely
strcpy and strcat write as many bytes as the source has, whatever the size of the destination. snprintf is the simplest safe replacement — it never writes more than the given size and always terminates the result:
#include <stdio.h> #include <string.h> int main(void) { char path[24]; const char *folder = "/home/sam"; const char *file = "notes.txt"; int n = snprintf(path, sizeof path, "%s/%s", folder, file); printf("%s (%d chars)\n", path, n); n = snprintf(path, sizeof path, "%s/%s", folder, "a-much-longer-name.txt"); if (n >= (int) sizeof path) { printf("truncated to \"%s\" (needed %d)\n", path, n); } return 0; }
/home/sam/notes.txt (19 chars) truncated to "/home/sam/a-much-longer" (needed 32)
When the return value is at least the buffer size, the output was cut short — check it whenever truncation would be a problem.
If you do use strcpy/strcat, first make sure the result fits:
#include <stdio.h> #include <string.h> int main(void) { char greeting[16] = "Hello, "; const char *name = "Ada"; if (strlen(greeting) + strlen(name) + 1 <= sizeof greeting) { strcat(greeting, name); } printf("%s\n", greeting); return 0; }
Hello, Ada
Avoid strncpy for general copying: when the source is too long it does not add a terminator, and when it's short it pads the rest of the buffer with zeros.
Searching
Search functions return a pointer to the match inside the string, or NULL if there's none. Subtracting the start of the string gives the position:
#include <stdio.h> #include <string.h> int main(void) { const char *email = "sam.rivera@example.com"; const char *at = strchr(email, '@'); if (at != NULL) { printf("user: %.*s\n", (int) (at - email), email); printf("domain: %s\n", at + 1); } const char *dot = strrchr(email, '.'); printf("top-level domain: %s\n", dot + 1); printf("contains \"river\"? %s\n", strstr(email, "river") ? "yes" : "no"); return 0; }
user: sam.rivera domain: example.com top-level domain: com contains "river"? yes
%.*s prints only a given number of characters. Pointers are explained in lesson 31; for now, read at - email as "how far into the string the @ is".
Splitting with strtok
strtok splits a string into tokens separated by any of the given characters. The first call passes the string; later calls pass NULL to continue:
#include <stdio.h> #include <string.h> int main(void) { char line[] = "red,green,,blue; yellow"; for (char *tok = strtok(line, ",; "); tok != NULL; tok = strtok(NULL, ",; ")) { printf("[%s]\n", tok); } return 0; }
[red] [green] [blue] [yellow]
Be aware: strtok modifies the string (it writes '\0' over each separator), so it can't be used on string literals; it skips empty fields; and it keeps hidden state, so you can't split two strings at once. POSIX's strtok_r and C23's strtok_s fix the last issue. For CSV with empty fields, scan with strchr or strcspn yourself.
Trimming
#include <stdio.h> #include <string.h> #include <ctype.h> char *trim(char *s) { while (isspace((unsigned char) *s)) s++; // skip leading spaces char *end = s + strlen(s); while (end > s && isspace((unsigned char) end[-1])) end--; *end = '\0'; // cut trailing spaces return s; } int main(void) { char input[] = " hello world \n"; printf("[%s]\n", trim(input)); return 0; }
[hello world]
Byte functions
The mem functions work on a given number of bytes and don't care about terminators — use them for arrays of any type:
#include <stdio.h> #include <string.h> int main(void) { int counts[5]; memset(counts, 0, sizeof counts); // all bytes to zero char buffer[] = "abcdef"; memmove(buffer + 2, buffer, 3); // overlapping copy is fine with memmove printf("%d %d | %s\n", counts[0], counts[4], buffer); return 0; }
0 0 | ababcf
Use memmove when source and destination might overlap; memcpy is undefined in that case.
Try it yourself
Count how many times a word appears in a sentence using strstr in a loop.
"the" appears 3 times