Putting the fun back in C with unity builds
In last month’s post I mentioned that in my opinion, the holy grail of
programming is C with classes. There is something incredibly fun about
programming in C that other languages just don’t have, but I will admit
that what it is missing are classes. You can work around this by using
struct
s and function pointers, but it’s just … not the same.
There is of course C++, the one true successor to C that adds classes.
We are now dealing with instances, references, namespaces and what not.
Writing clean C++ code is often an exercise in what I call “keeping the
compiler happy.” So Brian (pictured on the right) and me decided to start
using it in a more hackish way, if only to put the fun back in C.
So, I’m working on this project, and the codebase quickly grew to a couple of thousand lines of code. Nothing special there, as even hobby projects are easily 10k LoC or 50k LoC or more. My aging computer is taking longer and longer to build the code, up to the point where I just wish I had a C++ interpreter rather than a compiler.
“My code is compiling.”
The idea of C/C++ is that you write multiple translation units and compile those into object files. The header files just have declarations, the C source files contain the implementation. The linker will combine the object files to create the final executable. Rebuilding a project should be fast because only those objects for which you changed code need recompilation. In practice, it takes quite some work to get the job done. On top of that, in C++ the headers contain lots of code because of templated classes. All those headers need to be parsed and compiled over and again. Building becomes a chore, even if you only include what you truly need.
Enter the “unity build” system. Also known as a major hack, unity builds bring compile times back from minutes to near instant at best, and seconds at worst. How is this possible? That endless stream of C++ headers, we are only going to build that once. Even on my aging computer, the compiler is surprisingly fast at doing stuff only once.
How to setup a unity build system
For a unity build, we are not going to write any traditional header files.
Writing header files is just a chore, and considered a waste of time as
they don’t serve any real purpose in a unity build system. Instead, if you
need a struct or class, just put it in the top of the .c
or .cpp
source
file. Any typedefs global to the project may go into a single header named
types.h
, but unlike traditional C, these headers are never included in
every source file. No need to worry about any standard includes either; just
write straight C code into the unit. Each unit is included into a master
file for the project. That master file is the only file that is fed to the
compiler, and it will build surprisingly fast.
// master.c
#include <stdlib.h>
#include <stdio.h>
#include "types.h"
#include "debug.c"
#include "mycode.c"
#include "main.c"
// end
The easiest way of using a unity build system is for new projects.
Converting existing codes can be tricky. A reason for this is that
the order of includes is more important than ever, and there are no
include guards. Another reason is that all static symbols now appear
in a single flattened namespace. You can’t use static
at the unit
level in a unity build system. Choose your (static) symbol names wisely;
prefix them with the unit name, for instance.
The unity build system doesn’t help in creating reusable code. It is for this
reason that schooled C programmers say it’s a total hack job. That may be so,
but it is a complete joy to bang in make
and instantly see the shell prompt
return—ready to run that code and take it for a test drive. It gives a
spin to C, nearly turning it into a scripting language.
It puts the fun back in C. Maybe we’re not supposed to do it this way, but maybe that’s why it’s so much fun.