Lesson 2 ยท ~15 minutes ยท ties to mission: understand how memory works

strcat, Unrolled

You've called strcat(a, b) before. Now write the gears yourself โ€” and meet the two-pointer copy idiom, the most repeated pattern in C.

The code we're studying

char dest[20] = "Hello";
char *str2 = "World";

char *p = dest;            // 1. p starts at the beginning of dest
while (*p != '\0') {        // 2. Advance p until it points at dest's '\0'
    p++;
}

char *q = str2;            // 3. q reads from str2
while (*q != '\0') {        // 4. Copy in lockstep
    *p = *q;
    p++;
    q++;
}

*p = '\0';                  // 5. Re-seal the string

printf("Concatenated: %s\n", dest);   // โ†’ HelloWorld

Good news: you already know what this does. It's strcat(dest, str2) written out by hand. The book is making you build the engine so you can see every tooth on the gears.

The shape of it โ€” three acts

ACT 1 ยท steps 1โ€“2
Find the end. Walk p forward until it lands on dest's existing '\0'.
ACT 2 ยท steps 3โ€“4
Copy in lockstep. Two pointers step together: read from q, write to p, advance both.
ACT 3 ยท step 5
Re-seal. Write a fresh '\0' at the new end. Mandatory.

Act 1 is literally your mystrlen pointer-walk โ€” but instead of counting, you just keep the pointer. You already know this move. The only new idea is Act 2.

Watch the bytes move

Here is the whole lesson, as a diagram. Before Act 2 โ€” p has walked to dest's terminator, q sits at the start of str2:

dest: H e l l o '\0' ? ? ? ? ? ? ? ? ? ? ? ? ? ? index: 0 1 2 3 4 5 6 7 8 9 10 19 ^ p โ† landed right on the old '\0' str2: W o r l d '\0' ^ q

During Act 2 โ€” five iterations of *p = *q; p++; q++;. Notice the old terminator at index 5 gets overwritten on the very first copy:

iter 1: dest[5] = 'W' โ† the old '\0' is clobbered (that's fine) iter 2: dest[6] = 'o' iter 3: dest[7] = 'r' iter 4: dest[8] = 'l' iter 5: dest[9] = 'd' โ†’ p advances to [10], q hits '\0', loop exits

After Act 3 โ€” *p = '\0' writes the new terminator at index 10:

dest: H e l l o W o r l d '\0' ? ? ? ? ? ? ? ? ? index: 0 1 2 3 4 5 6 7 8 9 10 19 ^ p (now points at the new '\0')

printf("%s", dest) reads from index 0 until it hits '\0' at index 10 โ†’ prints HelloWorld. The garbage bytes at indices 11โ€“19 never matter โ€” the '\0' tells every string function where to stop.

The one subtle thing

Act 2 overwrites the old '\0'. That's not a bug โ€” it's the plan. The terminator of "Hello" only existed to mark where the string used to end. Once we're appending, that position is the start of new data. Which is exactly why Act 3 is mandatory: forget *p = '\0'; and the string has no end โ€” printf walks off into garbage. Forget the seal, get demons.

The idiom you'll see everywhere

*p = *q;     // read what q points at, write it where p points at
p++;         // advance the write head
q++;         // advance the read head

This two-pointer lockstep is the C idiom for "move bytes from here to there." It's not just strcat โ€” it's strcpy, it's memcpy, it's every custom buffer copy you'll ever write. Two heads on a zipper. Once you see it, you'll see it everywhere.

You'll see it compressed: *p++ = *q++; Postfix ++ binds tighter than *, so this means "copy, then advance both." Grown-up C code uses it constantly. Readable once you know it; a trap until you do. Keep writing the three-line version for now โ€” clarity beats cleverness while you're learning. Switch to the compact form only when the long form starts to feel tedious.

Memory safety check (your Lesson 1 instinct)

Before any strcat โ€” hand-rolled or library โ€” run the checklist from the reference:

  1. Does dest have real storage? โœ… char dest[20] โ€” 20 real bytes on the stack.
  2. Big enough? "Hello" (5) + "World" (5) + '\0' (1) = 11 โ‰ค 20. โœ…

This is why your exercise_concatinate_inputs.c crashed and this doesn't. Same operation, same idiom โ€” the only difference is whether real, correctly-sized storage backs the write pointer. The principle is doing real work for you now.

Check yourself

โ‘  What happens if you delete Act 3 โ€” the final *p = '\0';?

Show answer The loop writes World into indices 5โ€“9, then stops. But index 10 still holds whatever garbage was there. printf("%s") keeps reading past 'd' until it happens to find a zero byte โ€” printing HelloWorld followed by random garbage, or crashing, or both. Act 3 is not optional. Every string you build by hand must end in '\0'.

โ‘ก After Act 1, where does p point โ€” and what's stored there?

Show answer p points at dest[5], which holds '\0' (the terminator of "Hello"). That's the correct insertion point: the first slot of "new" data. The first copy (iter 1) overwrites this '\0' with 'W' โ€” which is fine, because Act 3 will place a new terminator further along.

โ‘ข Why does the loop test *q != '\0' and not *p != '\0'?

Show answer q walks the source (str2). We stop copying when the source runs out โ€” i.e. when q reaches its '\0'. Testing *p would be wrong: p walks the destination, whose contents are partially garbage. The copy length is governed by the source, always.

โ‘ฃ What if dest were only 8 bytes โ€” char dest[8] = "Hello";?

Show answer Undefined behavior. "Hello" uses 6 bytes (5 + '\0'); dest[8] has 2 free slots. Appending "World" (5 chars) writes past the end into memory that isn't yours. Same bug as common_str_ops.c from Lesson 1 โ€” bug (2), not enough storage. The hand-rolled strcat has no idea how big dest is. Neither does the library version. Buffer sizing is always the caller's job.

๐Ÿ› ๏ธ Hands-on โ€” turn it into a function

Make the code reusable by wrapping it in a function:

void mystrcat(char *dest, const char *src) {
    // ... your code here ...
}
  1. The body is your steps 1โ€“5 almost verbatim โ€” just use the parameter names dest and src instead of dest/str2.
  2. Call it from main: mystrcat(dest, str2); then print.
  3. If it prints HelloWorld, you own the pattern.
  4. Bonus: write a second call โ€” mystrcat(dest, "!!!"); โ€” right after the first. Confirm dest now reads HelloWorld!!!. (Why does it work twice? Because Act 1 re-finds the new end each time.)
Compare with a model solution
#include <stdio.h>

void mystrcat(char *dest, const char *src) {
    char *p = dest;
    while (*p != '\0') {     // Act 1: find the end of dest
        p++;
    }
    const char *q = src;
    while (*q != '\0') {     // Act 2: copy in lockstep
        *p = *q;
        p++;
        q++;
    }
    *p = '\0';               // Act 3: re-seal
}

int main(void) {
    char dest[20] = "Hello";
    const char *str2 = "World";

    mystrcat(dest, str2);
    mystrcat(dest, "!!!");
    printf("Concatenated: %s\n", dest);   // โ†’ HelloWorld!!!
    return 0;
}

Notice const char *src: we promise never to write through src. That's the same const instinct from Lesson 1, applied at the function boundary โ€” and it lets you pass a string literal ("!!!") without warning, because literals are const-qualified at heart.

What you can now do Read strcat, strcpy, and any hand-rolled copy loop and see the same three-act structure underneath: find the destination's end, copy in two-pointer lockstep, re-seal with '\0'. You've also seen the compact idiom *p++ = *q++; and know what it expands to. That idiom will recur for the rest of your C life.
Questions? Ask your agent. Anything murky โ€” "why does *p++ = *q++ work left-to-right?", "what's the difference between const char * and char * const?", "how would I write mystrcpy with the same pattern?" โ€” type it and we'll dig in. The two-pointer idiom is worth knowing cold; everything downstream builds on it.
Sources & further reading
ยท cppreference: strcat โ€” what the library version guarantees (and conspicuously does not: bounds checking).
ยท Beej's Guide to C Programming โ€” pointers, arrays, and strings, explained humanely.
ยท comp.lang.c FAQ โ€” Arrays and Pointers โ€” the canonical answers to "are arrays and pointers the same?" (spoiler: no, and strcat is where you feel it).
ยท Companion cheat sheet ยท Lesson 1: pointer vs array storage