C string.h Library
The C string.h header provides a standard set of functions for working with null-terminated byte strings and raw memory blocks. Because C has no built-in string type, every program that manipulates text — copying, comparing, searching, splitting, or measuring it — relies on this library. Understanding it deeply, including its sharp edges around buffer sizes and null termination, is essential to writing correct and safe C programs.
Overview / How it works
In C, a “string” is simply an array of char whose end is marked by a special sentinel byte, '\0' (the null character), rather than by a stored length. This is called a null-terminated string, and it is the convention that every function in string.h assumes. There is no separate “string” data type — a string is just a char * or char[], and the library functions figure out where it ends by scanning forward until they hit that zero byte.
This design has two big consequences you must internalize. First, the buffer holding a string must always be at least one byte larger than the visible text, to make room for the terminator. A 5-character word like "Hello" needs a 6-byte array. Second, none of the string.h functions know how big your destination buffer actually is unless you tell them (by passing a size, as with strncpy) — they will happily read or write past the end of an array, silently corrupting memory. This class of bug, the buffer overflow, is one of the most common and most dangerous mistakes in C.
Internally, most string.h functions are implemented as tight loops over bytes. strlen walks memory one byte at a time (real-world implementations often use word-sized or SIMD tricks to check several bytes per iteration) until it finds a zero byte, then returns the number of bytes it walked. strcpy copies bytes one at a time from source to destination until it copies the terminating zero. strcmp walks both strings in lockstep, comparing byte by byte, and stops at the first mismatch or at the shared end. Functions whose names start with mem (like memcpy and memset) do not look for a null terminator at all — they operate on a fixed number of bytes that you specify, which makes them suitable for binary data as well as text.
string.h functions fall into a few natural families:
- Length and copying:
strlen,strcpy,strncpy,strcat,strncat - Comparison:
strcmp,strncmp - Searching:
strchr,strrchr,strstr,strtok - Raw memory:
memcpy,memmove,memset,memcmp
Syntax
All of these functions live in <string.h>, so every program that uses them must include it. The table below shows the general shape of the most-used functions.
| Function | Prototype | Description |
|---|---|---|
| strlen | size_t strlen(const char *s); |
Returns the number of characters before the null terminator. |
| strcpy | char *strcpy(char *dest, const char *src); |
Copies src (including the terminator) into dest. No bounds checking. |
| strncpy | char *strncpy(char *dest, const char *src, size_t n); |
Copies at most n characters; does not guarantee a terminator if src is longer than n. |
| strcat | char *strcat(char *dest, const char *src); |
Appends src to the end of dest. No bounds checking. |
| strcmp | int strcmp(const char *a, const char *b); |
Returns 0 if equal, negative if a < b, positive if a > b. |
| strchr | char *strchr(const char *s, int c); |
Finds the first occurrence of character c, or NULL. |
| strstr | char *strstr(const char *hay, const char *needle); |
Finds the first occurrence of a substring, or NULL. |
| strtok | char *strtok(char *s, const char *delims); |
Splits a string into tokens across repeated calls (mutates s). |
| memcpy | void *memcpy(void *dest, const void *src, size_t n); |
Copies exactly n bytes; regions must not overlap. |
| memset | void *memset(void *s, int c, size_t n); |
Fills n bytes of s with the byte value c. |
Examples
Example 1: The basics — length, copy, concatenate, compare
#include <stdio.h>
#include <string.h>
int main(void) {
char greeting[50] = "Hello";
char name[] = ", World!";
printf("Length of greeting: %zu\n", strlen(greeting));
strcat(greeting, name);
printf("After strcat: %s\n", greeting);
char copy[50];
strcpy(copy, greeting);
printf("Copy: %s\n", copy);
if (strcmp(greeting, copy) == 0) {
printf("greeting and copy are equal\n");
}
return 0;
}
Output:
Length of greeting: 5
After strcat: Hello, World!
Copy: Hello, World!
greeting and copy are equal
This walks through the four workhorses of the library. strlen reports 5 because it counts only the visible characters, not the terminator. strcat finds the end of greeting and appends name right after it — note that greeting was declared with 50 bytes of room, far more than needed, precisely because strcat does not check space for you. strcpy then duplicates the combined string into copy, and strcmp confirms the two buffers hold identical text (it returns 0 for equality, which is why the comparison is written as == 0).
Example 2: Safer copying and searching
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Programming in C is fun";
char dest[10];
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
printf("Truncated: %s\n", dest);
char *pos = strstr(src, "in C");
if (pos != NULL) {
printf("Found substring at offset: %ld\n", (long)(pos - src));
}
char *ch = strchr(src, 'f');
if (ch != NULL) {
printf("First 'f' at offset: %ld\n", (long)(ch - src));
}
return 0;
}
Output:
Truncated: Programmi
Found substring at offset: 12
First 'f' at offset: 20
Here strncpy is given an explicit limit — sizeof(dest) - 1, leaving one byte for the terminator — so it can never overflow dest. Because src is longer than that limit, strncpy does not add a terminator on its own, so the program adds one manually on the next line; skipping that step is a classic bug (see Common Mistakes). strstr and strchr both return a pointer into the original array, so subtracting src from the returned pointer gives the numeric index where the match starts.
Example 3: Tokenizing a line of data
#include <stdio.h>
#include <string.h>
int main(void) {
char data[] = "apple,42,3.14,banana";
char *token = strtok(data, ",");
int count = 0;
while (token != NULL) {
printf("Token %d: %s\n", count + 1, token);
count++;
token = strtok(NULL, ",");
}
printf("Total tokens: %d\n", count);
return 0;
}
Output:
Token 1: apple
Token 2: 42
Token 3: 3.14
Token 4: banana
Total tokens: 4
This is the pattern used to split delimited text such as CSV lines. The first call passes the string to tokenize; every call after that passes NULL, which tells strtok to continue from where it left off in the same buffer. This works because strtok keeps hidden internal state and overwrites each delimiter in data with a '\0' as it goes — which is exactly why data must be a modifiable array (a string literal assigned to a char * would crash here) and why the original text in data is destroyed after tokenizing.
Under the hood: null termination and memory
Every function above ultimately reduces to one of two loop shapes. The “str” functions loop until they see a zero byte: strlen increments a counter, strcpy copies-and-checks, strcmp compares-and-checks. This means their cost is proportional to the length of the string, and they can run off the end of memory entirely if you pass a buffer that was never properly terminated — there is no way for these functions to know where your array “should” stop, only where the data actually stops (the next zero byte in memory, wherever that happens to be). The “mem” functions instead loop a fixed number of times, given explicitly as an argument, and never look at the contents to decide when to stop — that is what lets them safely handle binary data (including embedded zero bytes) and why you must always tell them the correct byte count. Also note that memcpy assumes the source and destination do not overlap; if they might overlap (for example, shifting elements within the same array), use memmove, which is guaranteed to work correctly even then, typically by copying in the safe direction internally.
Common Mistakes
Mistake 1: Overflowing a fixed-size buffer with strcpy
The wrong version copies a 13-character string into a 6-byte buffer. strcpy does not check the destination’s size, so it writes 7 bytes past the end of buf, corrupting adjacent memory — undefined behavior that might crash immediately, corrupt unrelated variables, or appear to “work” until it doesn’t.
char buf[6];
strcpy(buf, "Hello, World!"); /* buf is far too small: undefined behavior */
printf("%s\n", buf);
The fix is to copy at most as many bytes as the buffer can hold, and always terminate manually, since strncpy will not do it for you when the source is longer than the limit:
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[6];
strncpy(buf, "Hello, World!", sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
printf("%s\n", buf);
return 0;
}
Output:
Hello
Mistake 2: Comparing string contents with ==
== on two char arrays compares their addresses, not the characters inside them, so two separately-declared arrays holding the same text will compare as different:
#include <stdio.h>
int main(void) {
char a[] = "test";
char b[] = "test";
if (a == b) {
printf("Equal\n");
} else {
printf("Not equal\n");
}
return 0;
}
Output:
Not equal
Use strcmp to compare the actual characters, checking for a return value of 0:
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "test";
char b[] = "test";
if (strcmp(a, b) == 0) {
printf("Equal\n");
} else {
printf("Not equal\n");
}
return 0;
}
Output:
Equal
Best Practices
- Size destination buffers as
strlen(src) + 1at minimum, always leaving room for the null terminator. - Prefer bounded functions (
strncpy,strncat) orsnprintfover their unbounded counterparts (strcpy,strcat), and manually terminate the result sincestrncpymay not. - Never compare string contents with
==; always usestrcmporstrncmp. - Check the return value of
strchr,strstr, andstrtokforNULLbefore dereferencing or subtracting pointers. - Use
memcpyonly when source and destination cannot overlap; usememmovewhen they might. - Avoid
strtokin multithreaded code, since it relies on hidden static state shared across calls; prefer a reentrant alternative such asstrtok_rwhere available. - Cache the result of
strlenin a loop condition instead of recomputing it on every iteration, since a naive call rescans the whole string each time.
Practice Exercises
- Write a function
void reverse(char *s)that reverses a string in place using two index pointers, without calling anystring.hfunction exceptstrlen. - Write a program that counts how many times the character
'a'appears in a sentence, usingstrchrrepeatedly on advancing pointers rather than a plain loop over indices. - Write a program that uses
strtokto split the sentence"the quick brown fox jumps"on spaces and prints the total word count. Expected output:5.
Summary
- C strings are just
chararrays terminated by a'\0'byte; the library has no idea how big your buffer is unless you tell it. strlen,strcpy,strcat, andstrcmpare the core operations for measuring, copying, appending, and comparing strings.strncpy/strncattake an explicit size limit but do not guarantee null termination — you must add it yourself.strchrandstrstrsearch for a character or substring and return a pointer (orNULL) into the original string.strtoksplits a string into tokens across repeated calls, but it mutates the input and keeps hidden state, so it is not safe to use on string literals or across threads.memcpy,memmove,memset, andmemcmpwork on raw bytes rather than null-terminated text, making them suitable for binary data.- Buffer overflows from unchecked copies and appends are the most common and most dangerous
string.hmistake — always size your buffers deliberately.
