5.1.20 · D5C Programming

Question bank — String handling — char arrays, null terminator, strcpy, strcat, strlen, sprintf (dangers)

2,215 words10 min readBack to topic

Before the questions, four small pictures give you the mental model every answer leans on: how a string lives in memory, what "scanning" costs, why repeated append explodes, and what strncpy padding actually looks like.


The four pictures behind every trap

Figure — String handling — char arrays, null terminator, strcpy, strcat, strlen, sprintf (dangers)
Figure — String handling — char arrays, null terminator, strcpy, strcat, strlen, sprintf (dangers)
Figure — String handling — char arrays, null terminator, strcpy, strcat, strlen, sprintf (dangers)
Figure — String handling — char arrays, null terminator, strcpy, strcat, strlen, sprintf (dangers)

True or false — justify

A C string is a distinct data type like int or float.
False. A "string" is just a char array (or a char * pointing at one) that follows a convention: it ends with a '\0' byte (Picture 1). C has no dedicated string type — see Arrays vs Pointers.
The string "Hi" occupies exactly 2 bytes.
False. It occupies 3 bytes: 'H', 'i', and the invisible '\0'. The logical length is 2, but storage is always length + 1 (count the boxes in Picture 1).
strlen("Hello") returns 6 because it counts the terminator.
False. It returns 5. strlen counts the characters before the '\0' and stops the instant it reaches it, so the terminator is never counted.
char s[] = "abc"; and char s[3] = "abc"; are equivalent.
False. char s[] sizes itself to 4 bytes (room for '\0'), but char s[3] forces only 3 bytes so the terminator is silently dropped — s is now an unterminated array (a Picture 1 row with no STOP), and any string function on it is undefined behaviour (Undefined Behaviour in C).
Reading s[strlen(s)] gives the '\0' byte.
True. strlen returns the index of the first '\0', so s[strlen(s)] is exactly that terminator (value 0). That's the one index past the last real character that is still safely inside the array.
strcpy is safe as long as the source string fits in the destination's character count.
False. You must also fit the '\0'. char b[5]; strcpy(b, "Hello"); copies 6 bytes into 5 → buffer overflow. Size for len + 1.
snprintf(buf, sizeof buf, ...) can still overflow if the format is long.
False. That's the whole point: snprintf writes at most sizeof buf bytes and truncates the rest, always leaving a '\0' (when size > 0). It cannot overflow buf.
Two different string functions may leave you with a non-terminated buffer.
True. strncpy is the classic culprit (the red case in Picture 4): if the source is as long as (or longer than) the given n, it copies n bytes and writes no '\0'. sprintf overflowing also destroys termination.
sizeof buf always gives the number of bytes in the buffer.
False. Only when buf is a real array in the current scope. If buf is a char * parameter, the array has decayed to a pointer, so sizeof buf gives pointer size (typically 8) — see Pointers in C.
Appending with repeated strcat in a loop is efficient.
False. Each strcat first rescans the destination from the start to find its '\0', so building a string in a loop is — the "Shlemiel the painter" pattern of Picture 3 (Time Complexity).
strlcpy/strlcat are just slower aliases of strncpy/strncat.
False. strlcpy(dst, src, size) copies at most size-1 bytes and always null-terminates (unlike strncpy), and returns the length it tried to copy so you can detect truncation. They are BSD-origin safety functions, not standard C, but widely available.

Spot the error

char buf[5]; strcpy(buf, "Hello"); — what's wrong?
"Hello" needs 6 bytes (5 letters + '\0') but buf holds 5. strcpy writes past the end (into the grey cell of Picture 1) → buffer overflow, undefined behaviour, possible crash or exploit. Make buf[6] or use snprintf.
char b[3]; strcpy(b, "cat"); — is this okay?
No. "cat" is 4 bytes with its terminator; b holds 3. The '\0' (and possibly the last letter) is written out of bounds → overflow.
char c[6] = "Hi"; strcat(c, "there"); — trace the size.
Result "Hithere" needs 8 bytes (7 chars + '\0'), but c holds 6 → overflow. Destination must fit strlen(dst) + strlen(src) + 1.
sprintf(buf, "%d-%s", id, name); — why is this a landmine?
sprintf is never told how big buf is. A long name (or a wide %f, which can emit 300+ chars) overflows silently. Use snprintf(buf, sizeof buf, "%d-%s", id, name);.
void log(char *buf){ snprintf(buf, sizeof buf, "%d", x); }
— what's the subtle bug?
Inside the function buf is a pointer parameter, so sizeof buf is pointer size (8), not the caller's buffer size. Pass the true size as an extra argument across the boundary.
char s[3] = {'a','b','c'}; printf("%s", s); — safe?
No. All 3 slots are filled with letters, leaving no '\0' (a Picture 1 row missing STOP). printf("%s") walks past s looking for a terminator it will never find in bounds → undefined behaviour.
char *p = "abc"; p[0] = 'X'; — legal?
No. A string literal lives in read-only memory; writing through p is undefined behaviour (often a crash). Use char arr[] = "abc"; if you need a modifiable copy — see Arrays vs Pointers.
while ((dst[i] = src[i]) != '\0') i++; — will the terminator be copied?
Yes. The assignment happens before the test. When src[i] is '\0', dst[i] receives it too, and only then does the != '\0' test fail and stop the loop — so dst ends up terminated.
char big[4]; strncpy(big, "test", 4); — is big a usable string afterwards?
No. The source is exactly length 4, so strncpy fills all 4 boxes with t e s t and writes no '\0' (Picture 4, red case). Passing big to strlen/printf("%s") is then undefined behaviour.

Why questions

Why does C use a terminator byte instead of storing the length?
A char * is just an address with no room for a length field. The convention "stop at the zero byte" lets a bare pointer describe a string — cheap in 1972, at the cost of length queries (Picture 2, and Time Complexity).
Why must strcat overwrite the destination's existing '\0'?
The old terminator marked the end of dst. To fuse the two strings into one, that boundary must vanish; the copied '\0' from src becomes the new single end-marker.
Why is strlen inherently and never ?
No length is stored anywhere, so the only way to find the end is the box-by-box walk of Picture 2 — the work grows in step with the string length.
Why is forgetting the '\0' a security problem, not just a wrong answer?
Writing past the array corrupts neighbouring memory (return addresses, other variables — the grey region of Picture 1). Attackers exploit this to redirect execution — the classic buffer overflow attack.
Why does C not stop you from writing out of bounds?
C performs no bounds checking for speed; array access is raw pointer arithmetic. Out-of-bounds access is undefined behaviour — the compiler assumes you never do it.
Why do the n-suffixed functions (snprintf, strncpy, strncat) exist?
The n is a byte limit that caps how much they write, preventing overflow. They are the "seat-belt" versions of the unbounded originals — though strncpy still has the no-terminator trap of Picture 4.
Why does snprintf guarantee a terminator but strncpy does not?
snprintf is specified to always write a '\0' within the given size (when size > 0). strncpy instead pads with zeros only if the source is shorter than n (Picture 4, green case); if it's equal or longer, no terminator is added (red case).
Why do people reach for strlcpy/strlcat over the strn* functions?
Because strlcpy always terminates and reports the length it wanted, so you never get the silent no-STOP bug and can detect truncation in one check — closing the exact gap Picture 4 warns about.
Why does C's byte-oriented terminator get tricky with non-ASCII text?
Because '\0' marks the end at the byte level, not the character level. This is fine for UTF-8 (no valid multi-byte character contains a zero byte), but strlen returns the byte count, not the number of visible characters — "é" in UTF-8 is 2 bytes, so strlen says 2 for a one-glyph string.

Edge cases

What is strlen("") for an empty string?
0. The very first byte is already '\0', so the count stops immediately — but the array still occupies at least 1 byte for that terminator.
What does strlen do on a char array with no '\0' inside it?
It keeps scanning past the array's end (the grey region of Picture 1) until it happens to hit a zero byte somewhere in memory → undefined behaviour, a garbage (or huge) length, or a crash. strnlen(s, max) avoids this by capping the scan at max bytes.
What does strnlen(s, 3) return for s = "hello"?
3. strnlen walks like strlen but stops after at most max bytes, so it never runs off the end — it returns the smaller of the real length and max. It's the bounded, overflow-safe cousin of strlen.
What does snprintf(buf, 0, "%d", x) write?
Nothing into buf (size 0 means it may not even write the '\0'), but it still returns the number of characters it would have written — a handy trick to measure required size safely.
What happens with strcpy(dst, dst + 1) (overlapping copy)?
Undefined behaviour. strcpy assumes source and destination do not overlap; overlapping regions may read bytes it has already overwritten. Use memmove for overlaps.
What does snprintf do when the formatted output is exactly sizeof buf?
It writes sizeof buf - 1 characters plus a '\0', truncating the last real character. It never overflows, but you silently lose data — check the return value to detect truncation.
Is a single '\0' in the middle of an array a "short" string?
Yes, as far as string functions are concerned. char s[6]="ab\0cd"; has strlen 2; everything after the embedded '\0' is invisible to strlen, strcpy, and printf("%s"), even though the bytes are still stored.
Does strlen count characters or bytes for a UTF-8 string like "café"?
Bytes. "café" is 5 bytes in UTF-8 (é = 2 bytes), so strlen returns 5, not 4. C's functions are byte-oriented; counting visible glyphs needs a Unicode-aware library, not strlen.