Strings and the Standard Character Functions
This chapter explains how C represents strings (null-terminated character arrays), how string literals work, and how to use the core <string.h> functions safely. A deeper reference for each function appears in c.stdlib.4.
Why This Matters
C has no string type. A string is a convention: a contiguous sequence of characters terminated by a null byte ('\0'). Getting the null terminator, buffer sizing, and the strncpy/strncat semantics right is a matter of memory safety, not just style.
Prerequisites
c.core.21— arrays and decay.
Core Concept
Strings are null-terminated arrays
char s[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
char t[] = "hello"; /* same: 6 bytes, including the terminator */
A string literal "hello" is an array of char with static storage, of length 6 (5 characters plus the null terminator). It decays to char * in expressions, and its contents are not modifiable (modifying a string literal is UB).
String vs. character array
Not every char array is a string. It is a string only if it contains a null terminator within its bounds. Functions in <string.h> that operate on strings rely on that terminator.
Syntax
char *s = "hello"; /* s points to a string literal (do not modify) */
char a[] = "hello"; /* modifiable copy: 6 bytes */
The Core Functions
| Function | Purpose | Notes |
|---|---|---|
strlen(s) | length excluding null | must be null-terminated |
strcpy(d, s) | copy s into d | d must be large enough; no bounds check |
strncpy(d, s, n) | copy up to n chars | does NOT null-terminate if src >= n |
strcat(d, s) | append s to d | d must be large enough |
strncat(d, s, n) | append up to n chars | always null-terminates (writes n+1) |
strcmp(a, b) | compare lexically | returns <0, 0, >0 |
strncmp(a, b, n) | compare first n chars | |
strchr(s, c) | first occurrence of c | |
strrchr(s, c) | last occurrence of c | |
strstr(h, n) | first occurrence of n in h | |
strspn/strcspn/strpbrk | span/complement/break | see c.stdlib.4 |
strtok | tokenize (stateful!) | modifies the string, not reentrant |
memcpy/memmove/memset/memcmp/memchr | raw memory ops | sizes in bytes, not chars |
The full signatures, failure modes, and portability notes are in c.stdlib.4.
Examples
Correct string copy
#include <string.h>
#include <stdio.h>
int main(void)
{
char src[] = "hello";
char dst[32];
strcpy(dst, src); /* safe because dst is large enough */
printf("%s\n", dst);
return 0;
}
Expected output: hello.
The strncpy trap
#include <string.h>
#include <stdio.h>
int main(void)
{
char src[] = "hello world";
char dst[5];
strncpy(dst, src, sizeof dst); /* copies "hello", NO null terminator */
/* dst is not a valid string! */
printf("%.5s\n", dst); /* bounded print, avoids overread */
return 0;
}
strncpy copies exactly n characters (padding with zeros if the source is shorter) and does not null-terminate when the source is at least n characters. This is a frequent source of bugs. strncat is safer in that it always null-terminates.
Manual null-termination after strncpy
char dst[6];
strncpy(dst, src, sizeof dst - 1);
dst[sizeof dst - 1] = '\0';
How It Works
String functions scan for the null terminator (strlen, strcpy, strcat, strcmp) or operate on explicit byte counts (mem*, strn* variants). The null terminator is just a byte with value 0; there is nothing special about it except the convention that string functions stop there.
Variations
String literals and const
String literals have type char[N] in C (not const char[N]), but modifying them is UB. You should treat them as read-only, and declare pointers to them as const char * when possible.
Wide and UTF-8 strings
Wide strings use wchar_t and are covered in c.stdlib.10. UTF-8 is a byte encoding for Unicode that is compatible with null-terminated char strings (as long as no embedded zero bytes are misinterpreted), covered in c.stdlib.10.
Common Mistakes
- Forgetting space for the null terminator (
char s[5] = "hello";is an error
because it needs 6 bytes).
- Assuming
strncpynull-terminates. - Modifying a string literal.
- Passing an un-terminated buffer to
strlen/strcpy/strcat. - Using
strtokin a threaded or reentrant context (it uses static state).
Undefined Behavior
- Reading or writing past the end of a buffer with string functions.
VERIFIED
- Modifying a string literal.
VERIFIED - Passing a non-null-terminated buffer to
strlen/strcpy/strcat/strcmp.
VERIFIED
- Overlapping buffers with
strcpy/strcat(usememmovefor overlap).
Portability
- The null-terminated string convention is standard and portable.
strncpy's exact semantics (padding, no termination) are standard but
frequently surprising.
- Character encoding (ASCII vs. EBCDIC) is implementation-defined; ASCII is
universal on modern systems.
Under the Hood
String literals are typically placed in a read-only section (.rodata). The null terminator is just a zero byte. String functions compile to loops that scan for zero or copy until zero; modern compilers may optimize strlen/ strcpy to vectorized or built-in versions.
Practical Usage
- Always account for the null terminator in buffer sizes.
- Prefer
snprintf(from<stdio.h>) or explicit bounds-checked copy over
raw strcpy/strcat when sizes are not provably safe.
- Use
memcpy/memmovefor non-string binary data. - Use
strncpyonly when you understand its padding/no-termination semantics
and handle termination explicitly.
Exercises
1. Write a program that demonstrates the difference between char *s = "hello" and char a[] = "hello" (attempt to modify and observe). 2. Show the strncpy non-termination behavior and fix it. 3. Implement your own strlen, strcpy, and strcmp with a loop. 4. Write a safe string-copy function that always null-terminates and never overflows.
Deep Challenge
Implement strtok from scratch (including its static-state behavior) and then explain why it is not reentrant. Provide a reentrant alternative (like strtok_r on POSIX) and discuss the trade-offs.
Related Concepts
c.stdlib.4— full string.h reference.c.sec.1— buffer overflow.c.mem.memcpy— raw memory copying.c.stdlib.10— wide characters and UTF-8.
References
- ISO/IEC 9899:2018 §7.24 (string handling).
Verification
- Null-termination convention.
VERIFIED - String literal modification is UB.
VERIFIED strncpynon-termination semantics.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] String literals and null termination
- [ ] strlen/strcpy/strcat/strcmp
- [ ] strncpy/strncat semantics
- [ ] memcpy/memmove/memset/memcmp/memchr
- [ ] strtok and reentrancy
- [ ] Buffer sizing and overflow
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.str.literal | 0 | 6 |
| c.str.funcs | 0 | 6 |