No Functions to Call
The Atari 2600 shipped with 128 bytes of RAM and a cartridge slot. No
operating system. No standard library. No strlen, no memset, no
malloc.
If a game needed to copy memory — to scroll the playfield, to move a sprite's data between buffers — it wrote the loop. If it needed to measure the length of something, it counted. The 2600 programmer had no functions to call. Every operation on data was written, by hand, in the game itself.
That was not unusual. It was the state of the industry for over a decade. The NES had 2 kilobytes of RAM and no standard library either.
Every game that displayed a score — every routine that converted an
integer to tile indices, every scan of the sprite table to find an
empty slot — was doing by hand what we will shortly put behind
tci_strlen and tci_memset. The function did not exist. The
programmer wrote what it does, inline, every place it was needed,
because there was no other place to put it.
By the time the PlayStation arrived, C SDKs existed. The Psy-Q SDK — SN Systems[1] official PlayStation dev toolchain — included a C standard library.
Naughty Dog[2][3] used it — and found it too slow in the hot paths. The inner loops of Crash Bandicoot's[4][5] renderer and physics engine needed memory operations faster than what the SDK provided, so Andy Gavin[6] wrote his own.
They were building on Silicon Graphics[7] workstations — each one around $100,000 in the mid-1990s, the only machines that ran the PlayStation toolchain.[8] Not because any one function is hard to write. Because understanding each one is what lets you choose.


On constrained hardware you do not want the full standard library — you want the functions your project actually uses, and where a general-purpose version does more than you need, a leaner version written to your exact requirements costs less. You cannot make that trade-off for code you have not understood.
That is what libtci is. Not a replacement for the standard
library, which is better optimised than anything we will write and
which we will switch to when we start porting to console hardware.
An exercise in understanding it.
libtci reimplements libc — each function written clearly enough to
see why it works. When the time comes to use strlen instead of
tci_strlen, you will know what is happening inside it.
The implementation pages build libtci from the ground up, one group of functions at a time. Pointers first — the concept behind every function in the library — then memory operations, then strings, then characters, then the heap, then the static-library build. Every concept earns the next one. Start at Setup.
Seven Lines
The first function I wrote for libtci was tci_strlen. I had been
using strlen for several weeks at that point without thinking about
it. It was something the computer did.
When I sat down to write it, I stared at the function signature for a
few minutes before I understood what it was actually asking me to do.
A function that takes a const char * and returns size_t. A pointer
to a character, and a count. I wrote the body almost by accident:
size_t tci_strlen(const char *s)
{
size_t len;
len = 0;
while (s[len])
len++;
return (len);
}Seven lines of body — from the declaration to the closing brace.
The signature and opening brace are grammar; the logic is what sits
between them. s[len] is the character at position len in the
string. When s[len] is zero — when it hits the null terminator —
the loop stops. That is all strlen does. Walk memory one byte at a
time, count, stop at zero.
The standard library's version is faster. On modern CPUs it reads multiple bytes simultaneously and uses SIMD instructions to search them in parallel. But the behaviour is identical. A function I had been calling as if it were magic turned out to be a loop I could have written on my first day of C.
The rest of libtci is the same. memset is a loop that writes
bytes. strcpy is a loop that reads bytes until it hits a zero and
copies them. malloc is the interesting one — it calls into the
operating system to reserve memory — but even there, once you
understand what the pointer it returns actually is, the strangeness
disappears.
By the time I finished libtci, I understood what the standard library was: a collection of C code, mostly loops, written by people who cared about performance. Nothing magic. Just code.
The Project
Build libtci.a — a static library containing all of the following
functions, compiled from one .c file each, with libtci.h
declaring the full API:
Memory
tci_memset(s, c, n)— setnbytes ofstoctci_memcpy(dst, src, n)— copynbytes fromsrctodsttci_memmove(dst, src, n)— copynbytes, safe when regions overlaptci_memchr(s, c, n)— find first occurrence of bytecin firstnbytes ofstci_bzero(s, n)— zeronbytes ofstci_calloc(count, size)— allocatecount × sizezeroed bytes
Character classification
tci_isascii(c)— iscin the ASCII range (0–127)?tci_isalpha(c)— isca letter?tci_isdigit(c)— isca decimal digit?tci_isalnum(c)— isca letter or digit?tci_isspace(c)— iscwhitespace?tci_isupper(c)— iscan uppercase letter?tci_islower(c)— isca lowercase letter?tci_isprint(c)— isca printable character?tci_toupper(c)— convertcto uppercasetci_tolower(c)— convertcto lowercase
Strings
tci_strlen(s)— length ofsexcluding the null terminatortci_strcpy(dst, src)— copysrctodst, returndsttci_strncpy(dst, src, n)— copy at mostnbytes; pad with\0tci_strlcpy(dst, src, size)— copysrctodst, always null-terminate, return source lengthtci_strlcat(dst, src, size)— appendsrctodstwithinsizebytes, return total lengthtci_strcmp(s1, s2)— compares1ands2; return sign of differencetci_strncmp(s1, s2, n)— compare at mostnbytestci_strchr(s, c)— find first occurrence ofcinstci_strrchr(s, c)— find last occurrence ofcinstci_strdup(s)— allocate a copy ofstci_strndup(s, n)— allocate a copy of at mostnbytes ofstci_strnstr(haystack, needle, len)— findneedlein firstlenbytes ofhaystacktci_atoi(str)— convert string to integer
The final make must produce libtci.a with no warnings under
gcc -Wall -Wextra -g -std=c99.
The Tester
The tester lives in thecodingidiot-com/c01-the-toolkit. Clone it
once, then copy test.sh into your working directory:
git clone https://github.com/thecodingidiot-com/c01-the-toolkit.git
cp c01-the-toolkit/test.sh ~/c01-practice/
cd ~/c01-practice
bash test.shThe tester compiles a C test runner against your libtci.a and
compares every tci_* function against its libc or reference
counterpart across a wide input suite. All comparisons use exact output matching. Every
function must pass before the chapter is complete.
The Companion Repo
The solution/ directory contains all 29 source files, libtci.h,
and a complete Makefile. Build and test your own version first —
use solution/ to compare once you are done, not before.