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.
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:
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.
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.
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.
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.