Exercises — String handling — char arrays, null terminator, strcpy, strcat, strlen, sprintf (dangers)
Before we start, one tiny piece of vocabulary you will meet in the code below:
The counting rule you will use over and over:
The picture below makes those two numbers visible on a real array — look at how the red terminator box sits outside the length bracket but inside the bytes bracket.

Notice in the figure: the black double-arrow on top spans exactly the 3 visible characters (strlen = 3), while the red double-arrow underneath spans one box more — the extra box is the '\0'. That single extra box is the entire content of the +1 rule, and every exercise on this page is really about not losing track of it.
Level 1 — Recognition
(Just name the rule. No code tracing needed beyond counting.)
L1.1
Recall Solution
The word "code" has 4 characters: c o d e. strlen counts characters before the terminator, so it returns 4.
Bytes used to store the string : the bytes c o d e \0. The remaining bytes of the array exist but are unused by this string.
L1.2
Recall Solution
"Hi!" has characters (H, i, !). Minimum bytes . You must never forget the +1: an array of size 3 would have no room for '\0' and the string would be unterminated. (This is exactly the array drawn in the figure above.)
Level 2 — Application
(Apply the rule to a small computation or trace.)
L2.1
Recall Solution
strcat(a, b) appends b onto the end of a. It first walks a to its '\0' (after abc), overwrites that '\0', then copies de and a new '\0'. Result string: "abcde".
strlen("abcde") counts the 5 characters before the terminator ⇒ prints 5.
Byte check: the joined string needs bytes, and a is 20 bytes — plenty of room, so no overflow.
L2.2
Recall Solution
sprintf writes the formatted text "12345" into buf and appends '\0'. It returns the number of characters written, excluding '\0' — that is 5. So n == 5.
After writing, buf holds 1 2 3 4 5 \0, so strlen(buf) == 5. Output: 5 5.
Bytes used , so buf is large enough here. But note: sprintf never checked that — if the number had been 8+ digits it would have overflowed silently.
L2.3
Recall Solution
Array: O(79) K(75) \0(0).
n = 0:s[0] = 'O' != '\0'→ true, sonbecomes 1.n = 1:s[1] = 'K' != '\0'→ true, sonbecomes 2.n = 2:s[2] = '\0'→ false, loop stops.- Return
n =2. Matches:"OK"has length 2 and occupies 3 bytes.
Level 3 — Analysis
(Now you must reason about why — spot the bug, count the danger.)
L3.1
Recall Solution
Not safe — buffer overflow. "Robert" has characters (R o b e r t). strcpy copies all 6 characters plus the '\0', needing bytes. But name is only 6 bytes.
Overflow amount 1 byte. That single stray '\0' written one past the end of name is Undefined Behaviour in C — it may corrupt an adjacent variable, crash, or be silently ignored on one machine and explode on another. See Buffer Overflow & Memory Safety.
Fix: char name[7]; or snprintf(name, sizeof name, "%s", "Robert"); (which would truncate to "Rober" — no overflow).
L3.2
Recall Solution
Byte rule for strcat: destination must hold strlen(dst) + strlen(src) + 1.
strlen(c) = 2("12")strlen("3456") = 4- Needed bytes.
Capacity of
cis 8. Since , no overflow — the result"123456"(6 chars +'\0'= 7 bytes) fits. This is safe by exactly 1 spare byte.
L3.3
Recall Solution
The bug is sizeof buf. Inside store, buf is a char * parameter, not an array. sizeof on a pointer gives the pointer size — typically 8 on a 64-bit machine — regardless of how big the real buffer is. So snprintf is told the buffer is 8 bytes even if the caller passed a 100-byte array (unsafe: truncates unnecessarily) or a 4-byte one (unsafe: still overflows because 8 > 4). This is the Arrays vs Pointers decay trap.
Fix: pass the size explicitly:
void store(char *buf, size_t cap) {
snprintf(buf, cap, "%s", "hello");
}(size_t cap here is just "a byte-count", as defined at the top of the page.)
Level 4 — Synthesis
(Combine several ideas; design a small correct routine.)
L4.1
Recall Solution
Use snprintf with the capacity passed in — one bounded call does all three. (Recall size_t = "a byte-count", defined at the top.)
#include <stdio.h>
#include <stddef.h> // for size_t
// returns chars that WOULD be written (snprintf semantics)
int join3(char *buf, size_t cap, const char *a,
const char *b, const char *c) {
return snprintf(buf, cap, "%s%s%s", a, b, c);
}Why snprintf and not strcat? snprintf takes cap and guarantees it writes at most cap bytes total (including '\0'), truncating rather than overflowing. Three strcat calls would each need manual size checks — error-prone.
Minimum capacity for joining "C" (1) + " is " (4) + "fun" (3): total characters , giving the string "C is fun". Bytes needed 9. A char buf[9] is exactly enough.
L4.2
Recall Solution
#include <stddef.h> // for size_t
char *my_strncat_safe(char *dst, size_t cap, const char *src) {
size_t i = 0;
while (i < cap && dst[i] != '\0') i++; // 1) find dst's end, but never run past cap
size_t j = 0;
// 2) leave room for '\0': stop at cap-1
while (i < cap - 1 && src[j] != '\0')
dst[i++] = src[j++];
if (cap > 0) dst[i] = '\0'; // 3) always terminate
return dst;
}Guard 1 (i < cap): the original my_strcat scanned to dst's '\0' with no upper bound — if dst was already corrupt/unterminated, that scan itself could run off the array. We cap it.
Guard 2 (i < cap - 1): we stop copying one byte early so there is always space for the terminator. Writing up to cap - 1 leaves index cap - 1 free.
Guard 3 (if (cap > 0) dst[i] = '\0'): guarantees termination even if we truncated. The cap > 0 check avoids writing dst[-1] when someone passes capacity 0.
This is the same discipline that makes snprintf safe: bound the writes, reserve room for '\0', always terminate.
Level 5 — Mastery
(Deep reasoning: complexity, degenerate inputs, subtle correctness.)
First, a piece of notation you will use here:
L5.1
Recall Solution
Each strcat must first scan buf to its '\0' before it can append. On iteration (0-indexed), buf already contains characters. To find the end, strcat compares each of those characters against '\0' (all "not equal") and then makes one more comparison against the terminator itself (which is equal, ending the scan). So iteration costs exactly comparisons — the +1 is that final terminator check, and we must NOT drop it in the exact count.
Exact count for , summing the comparisons: So 10 character-comparisons to find the ends.
Asymptotic complexity. The exact total is the sum of over :
The dominant term is , so — dropping the constant and the smaller per the Big-O rule above — the loop is : the "Shlemiel the painter" quadratic blow-up from the parent note. (Keeping a running end-pointer instead would make the whole loop .) The point of separating the exact formula from the asymptotic is that the +1-per-iteration terms are what make the exact count 10 rather than 6, even though they vanish in the Big-O.
L5.2
Recall Solution
(a) The empty string "" is stored as a single byte '\0'. strlen sees e[0] == '\0' immediately and returns 0. A valid string of length 0 still needs 1 byte — the terminator. This is the smallest legal C string.
(b) u holds a b c with no '\0' (the initializer filled all 3 bytes with real chars). strlen(u) starts scanning and never finds a terminator inside the array — it keeps reading past the end into neighbouring memory. This is Undefined Behaviour in C: the returned "length" is garbage and the scan may crash. u is not a valid C string.
(c) With size 0, snprintf writes nothing to buf — not even the '\0' — because there is no room for even one byte. It leaves buf untouched but still returns 2 (the number of characters that would have been written, "hi"). This is why snprintf's size-0 case is safe: it refuses to write rather than overflow.
Score yourself
Recall Reveal the mastery checklist
If you got L1–L2 you know the rules (length vs bytes, +1).
L3 means you can spot overflows and the sizeof-pointer trap.
L4 means you can write bounded, always-terminating code.
L5 means you understand complexity and degenerate cases — the difference between a char array and a valid string, size-0 snprintf, and the quadratic strcat loop.
Missed any? Re-read that section of the parent note and retry.
Connections
- Pointers in C — every scan is really pointer arithmetic over the array.
- Arrays vs Pointers — the L3.3
sizeof bufdecay bug. - Buffer Overflow & Memory Safety — the consequence of every L3 overflow.
- Undefined Behaviour in C — L5.2's unterminated array.
- printf format specifiers —
sprintf/snprintfshare%d %s %f. - Time Complexity — L5.1's
strcatloop.