Skip to main content

Roadmap to C

This post is for you if you

It's also a companion post to Roadmap to game engine dev.

Why should I not learn C?

Because Rust exists.

Why should I learn C?

So you can appreciate why Rust does what it does.

Because C is the skeleton key that matches all locks. Because you can - in a very broad sense, stronger than is true for other languages - do anything in C.

Because you want to!

How did you (the writer) learn C?

The C Programming Language by Kernighan and Ritchie (henceforth "K&R") sucks. I mean, no, it's perfect and comprehensive and it gives you everything you need, without dumbing anything down. But it was written in 1988 for a very different audience, pre-internet era, which means it has quirks. The code itself is atrocious, for one. And the kind of programs they write and suggest you write are mind-numbingly boring. K&R are systems programmers who write programs to write programs to write programs. There is no semblance of play, no hint of the real world shining through in here. K&R is one part honest didactic tool, one part cold, hard reference monolith.

I did not learn C by reading K&R. I read K&R after I'd already basically understood the key concepts and implemented some small projects, like LZW compression/decompression; a custom drop-in malloc library; and socket-based networking. That was in a managed course/bootcamp thingy, with mentors and peers, partly following Harvard's CS50x https://www.edx.org/learn/computer-science/harvard-university-cs50-s-introduction-to-computer-science.

I approached this from a very basic level of programming competency: C is, basically, my native tongue. However, for someone with greater experience in programming, who's confident with a memory-managed language (Python, C#, JavaScript, anything really), who's ready to search and self-teach, I propose a different roadmap.

How should I (the reader) learn C?

Get a copy of K&R. You can find it on the first page of any respectable search engine. Just make double sure it's the Second Edition - "ANSI C!", it exclaims with pride, eager to show you the future of programming.

Go through the roadmap below until you find something you don't understand. Then open up K&R and read up to where it's explained, and/or search the internet, and/or ask a friend or mentor.

That last thing is important. If at all possible, don't do this alone. It's really boring and discouraging on your own. (If you're a normal person, learning C is boring and discouraging even with friends, but at least this way you can suffer together.)

Do some of K&R's exercises when you see them, but not the really annoying or boring ones. Don't drain your soul - just make sure you're actually absorbing the prerequisites as they come up, so that you develop a mental map of the language.

The items are very roughly ordered. Consider them also as a to-do list. You might need to jump forwards and back on occasion, and should always keep the whole in mind. Each group of bullet points is reinforced and/or taught by the project below it.

The roadmap

Start!

PROJECT: Hello World. Read name on command-line input and print it back out. And... compile the program. K&R - incredibly - does not teach you this at all. It was written in the days when a "compiler" was a boxful of 3.5" discs that you mail-ordered or borrowed from your university faculty, and there were 20 different compilers that all sucked in different ways, and they all had completely different interfaces - so it didn't make sense to write about them in a book about the C language. Luckily, today, we have GCC and Clang (and, in a pinch, MSVC). Figure out which is appropriate for you.

PROJECT: make a program that calculates the first N prime numbers using trial division. Hard-code N using a #define. Compile with different optimisation levels. Measure how long they all take to run.

PROJECT: update your prime-calculator to not #define n but instead const int n = .... Now you'll need to use malloc(), where you didn't before - why? That's annoying, but conversely, what problems were there with the #define before?

PROJECT: implement dynamically growable strings. Make a memo program that listens to what you type in on the command-line, concatenates it all together, and, when given a specific command, either prints it or writes it to a file. (Or something more interesting that you think of.)

PROJECT: implement linked lists. Then implement trees. Use either or both of these for a program which walks a directory and all subdirectories, remembering the name + size + etc. of each file; then prints them all out in a simple (horizontally-indented) tree structure. Basically, make ls to your personal taste.

PROJECT: implement LZW compression/decompression. Add debug printing, but only print it when DEBUG is defined to the preprocessor. Compile with/without DEBUG, without changing source code.

PROJECT: write your own printf(), using fputc(). Doing this exhaustively is really annoying. Just do the fun parts, and, y'know, the format of regular printf() sucks, so make up your own! (Bonus: support UTF-8 strings. Start here: https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/)

PROJECT: you're now ready to follow the C half of Crafting Interpreters https://craftinginterpreters.com/! Read this and discover your inner compiler writer. The first half walks through a biggish project in Java. Follow along and redouble your appreciation for how much more convenient higher-level languages are - no Java experience needed. The second half does the same thing but different, in C, giving you a sense for building a big project and reinforcing everything learned so far. Just don't get too absorbed and forget to come back here!

PROJECT: implement malloc() and free(), using brk() or sbrk() or HeapAlloc() or GlobalAlloc() (why do those functions not appear on cppreference?). Use any algorithm you feel like - as dumb as you want, or go wild and do some research.

PROJECT: create a library with your malloc()/free(). Make one of your earlier programs use your malloc()/free(), without changing their source code, by compiling with specific link order (and possibly include path). Now, you'd like to verify it worked and used your implementation, not built-in, by adding printf() statements... but you can't (why?). Instead, verify it by compiling with debug symbols and attaching a debugger (either gdb or the one built into your IDE... or both).

PROJECT: make a graphical game, in a window, with a square that moves around (with WASD/arrow keys) and picks up coins that randomly spawn. Do this with either SDL3 or GLFW. Evaluate both libraries and pick the one you vibe with best. Do not use OpenGL at this time - just use your library's built-in rectangle-drawing functions. Don't worry about displaying your score (that's pretty tedious). (Bonus: link your game dynamically.) (Bonus: compile GLFW/SDL3 from source.)

PROJECT: figure out what the hell an "OpenGL loading library" is and why it should exist. Use glad https://github.com/Dav1dde/glad and GLFW (not SDL3, this time) to draw the classic "hello triangle" in beautiful RGB. (This is, approximately, the path to https://learnopengl.com/Getting-started/Hello-Triangle - follow that webpage if you like.)

You now know everything. Congratulations.