Lesson 5 ยท ~12 minutes ยท ties to mission: build a small correct C program end-to-end

Hand-Rolled mystrcpy

char dest[20]; strcpy(dest, "Hello"); printf("%s\n", dest); // โ†’ Hello

strcpy(dst, src) copies every character from src into dst, including the terminating '\0'. It overwrites whatever was in dst before.

The difference from strcat strcat appends โ€” it must find the end of dest first, then copy. strcpy just copies from the beginning โ€” no search needed. That's one less act, which makes it the cleaner place to practice the two-pointer idiom.

Three ways to write it

You like writing things multiple ways (your three mystrlen implementations proved that). Here are three ways to do mystrcpy, each teaching something different.

Version 1 โ€” index form (the one you already know)

void mystrcpy_v1(char *dst, const char *src) {
    int i = 0;
    while (src[i] != '\0') {
        dst[i] = src[i];
        i++;
    }
    dst[i] = '\0';       // copy the terminator
}

Simple, readable. You've written loops exactly like this before. The '\0' is copied after the loop โ€” the loop stops at the terminator, and the explicit line seals the copy.

Version 2 โ€” pointer form (expanded, from lesson 2)

void mystrcpy_v2(char *dst, const char *src) {
    char *p = dst;
    const char *q = src;

    while (*q != '\0') {
        *p = *q;
        p++;
        q++;
    }
    *p = '\0';           // seal
}

Same as mystrcat's Act 2, but starting from position 0 instead of the end. No Act 1 needed โ€” the write pointer starts at the beginning. Same three-line lockstep you've been practicing.

Version 3 โ€” compact form (from lesson 4)

void mystrcpy_v3(char *dst, const char *src) {
    while (*src != '\0') {
        *dst++ = *src++;
    }
    *dst = '\0';
}

The two-pointer lockstep compressed into one line per iteration. This is the form you'll see in real C codebases. After lesson 4, you know exactly what *dst++ = *src++ expands to.

Bonus โ€” version 3b (the clever one-liner)

void mystrcpy_v3b(char *dst, const char *src) {
    while (*dst++ = *src++)
        ;   // empty body โ€” all the work is in the condition
}

This is the classic C trick. The loop condition does everything: copy a character, advance both pointers, test if the copied character was '\0' (i.e. zero). When '\0' is copied, the condition is false and the loop exits โ€” after writing the terminator into dst. No separate seal line needed.

Beautiful but evil This works. It's also the #1 reason people call C "unreadable." The entire copy happens inside a while condition. A newcomer sees it and thinks "that's an empty loop โ€” it does nothing." Use version 3 in your own code until the one-liner reads as naturally as English. For now, know it exists, don't write it.

See them side by side

index form
while (src[i] != '\0') {
    dst[i] = src[i];
    i++;
}
dst[i] = '\0';
compact pointer form
while (*src != '\0') {
    *dst++ = *src++;
}
*dst = '\0';

Every index i is a pointer offset. src[i] โ‰ก *(src + i). The pointer form just keeps the pointer moving instead of recalculating src + i each time. Same work, less arithmetic.

The safety checklist (lesson 1, again)

Before any strcpy โ€” hand-rolled or library โ€” the One Principle applies:

  1. Does dst have real storage? Must be an array or malloc'd memory.
  2. Big enough? strlen(src) + 1 bytes minimum. The +1 is for the '\0'.
char dest[20];
mystrcpy(dest, "Hello");   // strlen("Hello") + 1 = 6 โ‰ค 20 โœ“

char small[4];
mystrcpy(small, "Hello");  // 6 > 4 โ†’ UB, buffer overflow

Check yourself

โ‘  Why doesn't mystrcpy need an "Act 1" (find the end)?

Show answer Because strcpy copies from the beginning of dst, overwriting everything. strcat needs to find the end because it's appending โ€” it must know where the existing data stops. strcpy starts at position 0 unconditionally. That's the only structural difference.

โ‘ก In version 3b (while (*dst++ = *src++);), when exactly is the '\0' copied?

Show answer On the last iteration โ€” when src points at '\0'. The expression *dst++ = *src++ copies that '\0' into dst, advances both pointers, and yields '\0' (which is 0 = false). The while condition is false โ†’ loop exits. The terminator has already been written before the test. That's why no separate *dst = '\0'; is needed.

โ‘ข What's the return type of the real strcpy?

Show answer It returns char * โ€” a pointer to the destination. This lets you chain calls: strcpy(buf, strcpy(buf2, "hello")); (copy "hello" into buf2, then copy buf2 into buf). Most people don't chain it, but the return value is part of the interface. Check cppreference.

โ‘ฃ Can strcpy's source and destination overlap?

Show answer No โ€” undefined behavior. Example: strcpy(buf, buf + 2); โ€” the source starts inside the destination. As strcpy writes, it may overwrite source characters it hasn't read yet. The safe version for overlapping regions is memmove, which handles overlaps correctly.

๐Ÿ› ๏ธ Hands-on โ€” write all four versions and test them

Create a single file with all four mystrcpy implementations. Call each one and verify identical output:

  1. Write mystrcpy_v1 (index), v2 (expanded pointer), v3 (compact pointer), and v3b (one-liner).
  2. In main, declare a char dest[20] for each call. Copy "Hello, world!" into each.
  3. Print all four results. They should be identical: Hello, world!
  4. Edge case test: copy an empty string "" into each. Does each version handle it correctly? (It should โ€” the loop body never executes, and only the terminator is written.)
  5. Compare line counts: version 1 is ~6 lines, version 3 is ~4 lines, version 3b is ~2 lines. Same work, different verbosity.
What you can now do Write strcpy from scratch in four forms โ€” index, expanded pointer, compact pointer, and the one-liner. You see that every string-copy function is the same two-pointer lockstep underneath. And you know the safety rule: strlen(src) + 1 bytes must fit in dst.
Questions? Ask your agent. Anything murky โ€” "why does the real strcpy return char*?", "when would I use memmove instead?", "can I write mystrcpy with strlen instead of a loop?" โ€” type it and we'll dig in. After this, you're ready for malloc.
Sources & further reading
ยท cppreference: strcpy โ€” the real signature, return value, and overlap restriction.
ยท cppreference: memmove โ€” the safe version for overlapping copies.
ยท Beej's Guide to C Programming โ€” strings and pointers.
ยท Companion cheat sheet ยท Lesson 4: *p++ = *q++