Exercise: Implement strlen(), strcpy(), strcat(), memcpy()¶
Tools: GCC, Make
Goal¶
Reference solution and explanation for the string-functions exercise in 01-string-functions.
Implement four of the C library's string functions from scratch, strlen(), strcpy(), strcat() and memcpy(), and check each one from main() as it is written.
Two files differ from the work directory:
mystring.cholds the four implementations.main.cholds the calls that fill in itsTODOsections.
mystring.h and the Makefile are provided and are not modified.
Background¶
A C string is a byte sequence terminated by '\0' and it does not carry its length.
Any function that needs the length has to go and find it, byte by byte.
This is a property of the interface, not of any implementation, which is why it cannot be optimised away.
memcpy() is the counterexample in the same header: it is told n, knows nothing about '\0', and therefore never has to search for anything.
The program is built from two separately compiled object files, mystring.o and main.o, linked into the main executable.
main.c includes mystring.h for the declarations; the definitions only meet the calls at link time.
Build & Run¶
The work is done one function at a time: implement the TODO in mystring.c, fill in the matching TODO in main.c, then rebuild and rerun.
The checks in main.c compare against fixed expected values, so calling the libc function alongside yours, as the task suggests, is a way of seeing the expected value for yourself rather than a requirement.
Results and Explanations¶
The output¶
With all four functions implemented and all calls filled in:
[PASSED] strlen: empty string
[PASSED] strlen: hello string
[PASSED] strcpy: empty string
[PASSED] strcpy: hello string
[PASSED] strcat: empty string
[PASSED] strcat: hello string
[PASSED] memcpy: def byte array
[PASSED] memcpy: def string
[PASSED] memcpy: defghij string
Before anything is filled in, every line reads [FAILED]: the variables the checks look at are initialised to values that cannot pass (len = 0xFF, ret = NULL, buf = "abcde").
This is deliberate, so a check passes only once the call that should change its variable has actually been made.
The four functions¶
my_strlen() walks to the '\0' and returns the distance covered.
The terminator is not counted, so the loop stops on it rather than after it.
my_strcpy() puts the assignment in the loop condition:
The byte is copied first and the copied value tested second, so the '\0' is written and then ends the loop.
Copying the terminator is what makes dest a string rather than a pile of bytes.
The return value is dest, not the end of it — easy to get wrong, and every check in main.c tests ret == buf for exactly that reason.
my_strcat() finds the end of dest and copies src there:
One line, and it hides a cost — see below.
my_memcpy() takes typed pointers first, because void * can be neither dereferenced nor advanced:
unsigned char is the right choice: exactly one byte, no padding and no trap representations.
n is the only stopping condition, so an embedded '\0' is copied straight through, and while (n-- > 0) handles n == 0 without a special case.
The calls in main.c¶
Each TODO is one call, stored in the variable the following check reads:
len = my_strlen("hello");
ret = my_strcpy(buf, "hello");
ret = my_strcat(buf, "hello");
ret = my_memcpy(buf, "def", 4);
The three my_memcpy() checks build on each other, in the same buffer:
TODO 4acopies 3 bytes of"def": the letters only, no terminator.TODO 4bcopies 4 bytes, which includes the'\0'of the literal"def", sobufnow holds a string.TODO 4ccopies 7 bytes of"defghij", overwriting that'\0'. The check compares 8 bytes, and passes becausebuf[7]was zero from the initialiser:my_memcpy()wrote exactly 7 bytes and did not need to.
What my_strcat() costs¶
my_strlen(dest) rescans the whole of dest on every call, because a C string does not carry its length.
Appending a 16-byte chunk to an initially empty string, call i first walks 16 × i bytes to find where to write 16 more.
Appending N chunks scans ≈ 8N² bytes in order to copy 16N bytes of data: the loop is quadratic, not linear.
The fix is not a faster loop — it is not throwing the length away.
A caller that remembers the offset itself and uses my_memcpy() (or my_strcpy()) at that offset does linear work.
demo-copy-string measures exactly this difference.
References¶
man 3 strlen,man 3 strcpy,man 3 strcat,man 3 memcpy,man 3 memmove- Joel Spolsky, Back to Basics — "Shlemiel the painter's algorithm"