C fundamentals ยท lesson 06

Strings in C

C has no string type. A "string" is just an array of <code>char</code> with a zero byte at the end. Everything about strings in C follows from that one fact.

in progress
10 min interactive

The null terminator

When you write "hello" in C source code, the compiler stores 6 bytes: 'h', 'e', 'l', 'l', 'o', and then 0 โ€” the null terminator. That zero byte is how every string function knows where the string ends. There's no length field, no metadata. Just bytes until zero.

๐Ÿ’ก
Mental model: a C string is a pointer to the first character. Functions like strlen walk forward, byte by byte, counting until they find a 0. The string "ends" wherever that 0 happens to be.

char* vs char[]

These two look similar but behave differently:

c
char *p = "hello"; // pointer to a string literal in .text (read-only) char a[] = "hello"; // array on the stack, bytes copied there p[0] = 'H'; // undefined behavior โ€” modifying read-only memory a[0] = 'H'; // fine โ€” a[] is on the stack, you own those bytes printf("%zu\n", sizeof(a)); // 6 โ€” includes null terminator printf("%zu\n", sizeof(p)); // 8 โ€” just a pointer

char *p = "hello" puts a pointer on the stack pointing to the literal in the .text segment, which is read-only. Modifying it is undefined behavior โ€” your program will likely segfault. char a[] = "hello" copies the bytes onto the stack. You own them and can modify them.

๐Ÿšซ
Never modify a string literal. char *p = "hello"; p[0] = 'H'; is undefined behavior. The compiler may put string literals in read-only memory. On Linux/macOS this crashes immediately. Always use char[] if you need a mutable string.

Buffer overflows: the classic C bug

C string functions don't know how big your buffer is. strcpy copies until it hits a null terminator โ€” if the source is longer than the destination, it writes past the end of the buffer and overwrites whatever comes next on the stack or heap.

c
char buf[8]; // DANGER โ€” strcpy doesn't check the destination size strcpy(buf, "hello, world!"); // writes 14 bytes into an 8-byte buffer // overwrites 6 bytes past the end // SAFE โ€” strncpy limits the copy strncpy(buf, "hello, world!", sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; // always null-terminate manually // BETTER โ€” snprintf for formatted strings snprintf(buf, sizeof(buf), "%s", source);
โœ…
Rule: never use strcpy, strcat, or gets. Always use the size-bounded versions: strncpy, strncat, fgets, or snprintf. Pass the buffer size. Add a manual null terminator.

String length vs buffer size

These are two different numbers and confusing them causes bugs. strlen("hello") returns 5 โ€” the number of characters, not counting the null terminator. The buffer holding "hello" must be at least 6 bytes to include the null.

c
char s[] = "hello"; strlen(s) // โ†’ 5 (characters, no null) sizeof(s) // โ†’ 6 (bytes including null, only works on arrays) // To allocate heap space for a string copy: char *copy = malloc(strlen(s) + 1); // +1 for the null terminator! strcpy(copy, s);

See it byte by byte

Step through the visualization below. Each cell is one byte. Watch the null terminator, see what happens when you write \\0 early, and see the dangerous case where a buffer has no terminator at all.