🎯 Pointers & Memory
Strings: char arrays with a secret handshake
▶ Open the interactive lesson — free, no signupEvery username, chat message, and filename your programs will ever touch is text — and C stores text as a bare char array with a hidden stop sign at the end. Lose that stop sign and the string functions march through memory unchecked; that exact class of mistake helped the 1988 Morris worm knock out a tenth of the internet. This lesson hands you the convention, the toolbox, and the habits that keep your code off that list.
C has no string type. What it has is a convention: a string is a char array whose end is marked by a zero byte, written '\0' and called the null terminator. Every string function in existence — printf("%s"), the length-counter strlen, all of them — just walks forward from a starting address until it hits that zero.
▶ This spot has an interactive memgrid widget — open the interactive lesson to play with it.
Working with strings
#include <stdio.h>
#include <string.h>
int main(void) {
char s[] = "hi"; /* really {'h','i','\0'} */
printf("strlen: %zu\n", strlen(s)); /* chars before \0 */
printf("sizeof: %zu\n", sizeof s); /* whole array */
for (int i = 0; s[i] != '\0'; i++) /* the string walk */
printf("s[%d] = '%c' (%d)\n", i, s[i], s[i]);
return 0;
}$ gcc hi.c -o hi && ./hi strlen: 2 sizeof: 3 s[0] = 'h' (104) s[1] = 'i' (105)
Two different questions, two different answers: strlen counts characters before the terminator (2); sizeof measures the whole array including it (3). Forgetting the terminator's byte when sizing a buffer is a classic off-by-one.
🧠 Checkpoint: How many bytes does char s[] = "cat"; occupy?
- 3
- 4
- 8
- Implementation-defined
Show answer
4 — Three letters plus the hidden '\0' terminator = 4 bytes. Buffers must always budget that extra byte.
String literals are (effectively) read-only
The same-looking declarations below are deeply different. An array initialized from a literal gets its own writable copy of the bytes. A pointer assigned a literal points straight into the program's read-only data segment (.rodata — remember the memory map?):
char stack_copy[] = "hello"; /* ARRAY: bytes copied onto the
stack — yours, writable */
char *readonly = "hello"; /* POINTER into .rodata — the
literal itself, do NOT write */
stack_copy[0] = 'H'; /* fine: your copy */
readonly[0] = 'H'; /* UB — typically SIGSEGV */Modifying a string literal is undefined behavior. On typical systems the literal lives in a write-protected page, so readonly[0] = 'H' dies with a segfault. Defend yourself by declaring literal pointers as const char *s = "hello"; — then the compiler rejects the write at build time instead.
🧠 Checkpoint: char *p = "abc"; p[0] = 'x'; — what happens?
- p becomes "xbc"
- Compile error
- Undefined behavior — the literal is (effectively) read-only
- Nothing at all
Show answer
Undefined behavior — the literal is (effectively) read-only — The pointer aims at the literal in read-only storage; writing through it is UB and usually segfaults. Write const char *p and the compiler will catch the mistake for you.
The <string.h> toolbox
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[32] = "Hello"; /* room to grow */
strcat(buf, ", world"); /* append (must fit!) */
printf("%s (len %zu)\n", buf, strlen(buf));
char copy[32];
strcpy(copy, buf); /* copies incl. \0 */
printf("strcmp(\"apple\",\"banana\") = %d\n",
strcmp("apple", "banana"));
printf("copy equals buf? %s\n",
strcmp(copy, buf) == 0 ? "yes" : "no");
return 0;
}$ gcc toolbox.c -o toolbox && ./toolbox
Hello, world (len 12)
strcmp("apple","banana") = -1
copy equals buf? yes
# strcmp guarantees only the SIGN: any negative value is legalstrcmp does not return a boolean! It returns negative / zero / positive for less / equal / greater (think: "a minus b"). So if (strcmp(a, b)) is true when the strings differ — the exact opposite of what it looks like. Always write strcmp(a, b) == 0 for equality.
Buffer overflows: a 40-year-old wound
strcpy and strcat copy until they find '\0' — they never check whether the destination is big enough. Copy 20 bytes into a 8-byte buffer and you overwrite whatever lives next door: other variables, or the function's return address. That's not just a bug; overwriting the return address with attacker-chosen bytes is the classic security exploit.
The 1988 Morris worm — the first internet worm, which took down ~10% of the internet — spread partly through a buffer overflow in a string routine (gets). gets was finally removed from the C standard in C11, the only function ever expelled. Use bounded functions: snprintf, fgets, strncat.
🧠 Checkpoint: When does if (strcmp(a, b)) take the branch?
- When the strings are equal
- When the strings differ
- When a is longer than b
- Never — it doesn’t compile
Show answer
When the strings differ — strcmp returns 0 (falsy!) for equal strings and nonzero otherwise. The naked-strcmp condition reads like "if equal" but means "if different" — a beloved interview trap.
▶ This spot has an interactive editor widget — open the interactive lesson to play with it.
One string is a 1-D char array — so what's an array of strings? Time to stack arrays inside arrays.
▶ Practice this lesson interactively (with live gcc)