Worked examples — String handling — char arrays, null terminator, strcpy, strcat, strlen, sprintf (dangers)
The scenario matrix
Before any example, let us list every distinct situation a C string can be in. If a bug or a surprise exists, it lives in one of these rows.
| Cell | Case class | What is special | Covered by |
|---|---|---|---|
| A | Normal string, room to spare | length + '\0' easily fits buffer |
Ex 1 |
| B | Buffer exactly full | len + 1 == size (the boundary) |
Ex 2 |
| C | Off-by-one overflow | len + 1 > size by exactly one byte |
Ex 3 |
| D | Empty / degenerate string | length , first byte is '\0' |
Ex 4 |
| E | Missing terminator | array has no '\0' at all → runaway scan |
Ex 5 |
| F | Concatenation overflow | strlen(dst)+strlen(src)+1 > size |
Ex 6 |
| G | Limiting / cost behaviour | repeated strcat → quadratic time |
Ex 7 |
| H | sprintf output longer than buffer |
formatted text overflows silently | Ex 8 |
| I | sizeof trap across a function boundary |
char * param vs real array |
Ex 9 |
| J | Bounded snprintf truncation (the safe fix) |
output too long but no overflow | Ex 10 |
Two colour conventions used in every figure below:
Cell A — normal string with room

- Count
src's bytes."dog"isd o g '\0'= bytes. Why this step? Because the copy must move all bytes including the terminator — see the coral box in the figure. - Copy into
dst[8]. We write bytes into an -byte array. , so no overflow. Why this step? Cell A is defined by "room to spare" — this is the safe baseline against which every later bug is measured. my_strlen(dst)scansd(1) o(2) g(3) '\0'(stop)and returns . Why this step? Length counts characters before'\0', so the count is , not .
Recall Verify
Reveal ::: strlen("dog") == 3, copy needs bytes, ✓ no overflow.
Cell B — buffer exactly full (the boundary)

- Add the terminator.
"cat"=c a t '\0'= bytes. Why this step? Every string literal silently appends'\0'— you must budget for it. - Fit into
a[4]. bytes into bytes: exactly full,len + 1 == size. Why this step? This is the boundary case — legal, but there is zero slack. One more character and we are in Cell C. strlen(a)scansc(1) a(2) t(3) '\0'→ returns .
Recall Verify
Reveal ::: strlen("cat") == 3 and buffer size (exact fit) ✓.
Cell C — off-by-one overflow

- Count what must be written.
"Hello"=H e l l o '\0'= bytes. Why this step? The five letters are visible; the sixth byte ('\0') is invisible but mandatory. - Compare to the buffer. .
strcpywrites byte index past the array's last valid index . Why this step? Cell C is exactlylen + 1 > sizeby one — the most common real bug in C. - What C does: nothing to stop you. The dashed coral box in the figure (the neighbour's byte) gets a written into it → buffer overflow, undefined behaviour. Why this step? There is no bounds check (Buffer Overflow & Memory Safety); the corruption may crash now, later, or be exploited.
Recall Verify
Reveal ::: needed bytes , buffer , overflow amount byte ✓.
Cell D — the empty / degenerate string

- Layout of
"". The first byte is already'\0':\0 ? ? ?. Why this step? The empty string is not "no bytes" — it is one byte, the terminator, sitting at index . strlen(e)looks ate[0], sees'\0'immediately, stops with counter . Why this step? This is the degenerate limit of the counting loop: it never runs a single increment.strcpy(e, "")would copy exactly one byte (the'\0') — always safe, even into a -byte buffer.
Recall Verify
Reveal ::: strlen("") == 0 ✓; it still occupies byte (the terminator).
Cell E — missing terminator (runaway scan)

- Spot the trap.
{'a','b','c'}fills all slots. There is no'\0'anywhere. Why this step? Curly-brace init does not append a terminator when the array is exactly full — unlike a string literal. - Run
strlen. It countsa(1) b(2) c(3)then readsbad[3]— outside the array — and keeps going, counting whatever random bytes lie beyond until it accidentally meets a . Why this step? Without the stop signal the loop cannot terminate at ; it walks into your neighbour's memory (the dashed boxes). - Result: undefined — could print , , or crash. There is no correct answer. Why this step? Cell E shows that "the letters are right" is not enough: the terminator is the contract, not the letters.
Recall Verify
Reveal ::: intended length , but declared bytes with no terminator ⇒ 3 + 1 = 4 > 3, so no '\0' fits ⇒ scan is undefined ✓.
Cell F — concatenation overflow

- Measure the pieces.
strlen("Hi") = 2,strlen("there") = 5. Why this step?strcatglues them, so the final string is"Hithere"of length . - Compute bytes needed. result length bytes.
Why this step? The rule is
strlen(dst) + strlen(src) + 1— the is the single joined terminator. - Compare to buffer. ⇒ overflow by bytes (dashed boxes in the figure).
Why this step?
strcatfirst walks todst's'\0'at index , then copiest h e r e '\0'starting there — indices , but valid indices stop at .
Recall Verify
Reveal ::: 2 + 5 + 1 = 8 needed, buffer , overflow bytes ✓; strlen("Hithere") == 7.
Cell G — limiting cost behaviour (quadratic strcat)

- Recall
strcat's hidden scan. Before appending, it walksdstfrom the start to its'\0'. That is every single time (Time Complexity). Why this step? The append itself is cheap ( char), but finding the end is not. - Sum the scans. On iteration the current length is , so the scan costs about reads. Total: Why this step? Adding up uses the arithmetic-series formula — that is the "Shlemiel the painter" curve.
- Interpret. To append single characters we performed byte-reads — quadratic in , not linear. Why this step? This is the limiting behaviour cell: correctness is fine, but the cost blows up as grows.
Recall Verify
Reveal ::: ✓.
Cell H — sprintf output longer than the buffer
- Format the number.
%dturns12345into the characters'1' '2' '3' '4' '5'. Why this step?sprintfwrites text, not a raw int — five digits (printf format specifiers). - Add the terminator.
sprintfalways appends'\0'⇒ bytes written. Why this step? Even the safe count needs the ; here it makes the overflow worse. - Compare to buffer. ⇒ overflow by bytes.
sprintfnever sawbuf's size, so it cannot stop. Why this step? Cell H is thesprintf-specific danger: the size is simply not one of its arguments.
Recall Verify
Reveal ::: digits of , needed , buffer , overflow ✓.
Cell I — the sizeof trap across a function
- Array decays to pointer. When
storeis passed, the parameterbufis achar *, not an array (Arrays vs Pointers). Why this step? A pointer only holds an address — it forgot the array is bytes long. - Evaluate
sizeof buf. On a -bit machine,sizeofachar *is (pointer size), not . Why this step?sizeofon a real array gives its byte count; on a pointer it gives the pointer's own size. - Consequence.
snprintfis told the buffer is only bytes, so it truncates output at even though were available — a silent capacity bug (and would be catastrophic if the intent had been a plainsizeof-basedstrcpy). Why this step? Cell I shows the danger is not overflow but a wrong size number at a function boundary.
Recall Verify
Reveal ::: inside fill, sizeof buf == 8 (pointer), but real array sizeof store == 64 ✓ — they differ.
Cell J — the safe fix (snprintf truncation)
- Compute the would-be full length.
"log_2024.txt"= characters.snprintfreturns the length it would have written (excluding'\0'), son == 12. Why this step? This return value is how you detect truncation: ifn >= sizeof path, output was cut. - What actually lands in
path.snprintfwrites at mostsize - 1 = 7characters plus a guaranteed'\0':l o g _ 2 0 2 '\0'⇒path == "log_202". Why this step? Cell J is the safe counterpart to Cell H: instead of overflowing,snprintftruncates and always terminates. - Check for truncation. Since , the caller knows the name was truncated and can react (allocate more, error out).
Why this step? This is the whole point of the
n-functions — a seat-belt that reports the crash it prevented.
Recall Verify
Reveal ::: full length of "log_2024.txt" (so n == 12); stored chars , giving "log_202" ✓.
Connections
- Parent topic — the tools these examples exercise.
- Buffer Overflow & Memory Safety — Cells C, F, H are all overflows.
- Undefined Behaviour in C — why Cells C, E have no defined answer.
- Arrays vs Pointers — Cell I, the
sizeoftrap. - Time Complexity — Cell G, the quadratic
strcat. - printf format specifiers — Cells H, J, how
%d/%sexpand.