Obfuscated C
Written by David Evans, first published (last updated )
This project’s C code doesn’t look much like C code. Not only that; once all the
macros are expanded the code is just a single statement in a main function. It
doesn’t even use ?: or the comma operator! And yet it compiles without any
warnings and runs a workable (if very limited) game of
boulder dash. How
does it branch? How does it loop? How does it store variables and allocate
memory? How is it able to work at all?
This silly C project was inspired by an anonymous 2005 entry to the International Obfuscated C Code Contest. The original idea — “Memory Allocated by Stack Smashing” — comes from that entry; I’ve just extended some concepts and built a more complicated game on top.
Files
bd.c
// This uses ANSI codes to colour the output.
// If your terminal does not support ANSI codes, define NO_TERM_COLS at build time
// Print helpers (with/without ANSI codes)
// Initialise memory and call init
run(vars, init)
function(init) (
if (depth > vars + 1) (
do b = size / 5 // number of rocks
do c = size / 10 // number of walls
do allgems = wincount * 3 + size / 20 + 1
if (allgems + b + c + 1 > size) (
do allgems = size / 20 + 1
)
do player_x = width / 2
do player_y = height - 1
do gems = allgems
do dead = 0
for (loop_init, size)
goto(gameloop)
if (gems > best) ( do best = gems )
if (gems >= allgems) ( do wincount ++ )
if (dead == -3) (
break
) elif (dead != -2) (
do printf(" (best: %d) Press any key to restart.\n", best)
do getc(stdin)
)
goto(init)
) elif (argc == 3) (
do wincount = 0
do best = 0
do width = atoi(argv[1])
do height = atoi(argv[2])
do size = width * height
if (size > 1000) (
do printf("\nLarge grid, continue? (y/n) ")
if (getc(stdin) != 'y') (
do printf("\nTry 20x20.\n")
break
)
)
goto(init with size)
do printf(" Bye.\n")
) else (
do printf("Try:\n\t%s 20 20\n\n", argv[0])
)
)
function(gameloop) (
do printf("\n\n")
if (parameter) (
do map_at_player_offset(0, 0) = empty
switch (parameter) (
case (1) do player_y -- break
case (2) do player_x -- break
case (3) do player_y ++ break
case (4) do player_x ++ break
)
switch (map_at_player_offset(0, 0)) (
case (gem)
do gems ++
break
case (rock)
do map_at_player_offset((parameter == 4) * 2 - 1, 0) = rock
break
)
do map_at_player_offset(0, 0) = player
goto(gravity)
)
do b = ( // can move left?
player_x > 0 &&
(
map_at_player_offset(-1, 0) is soft ||
(
map_at_player_offset(-1, 0) == rock &&
player_x > 1 &&
map_at_player_offset(-2, 0) == empty
)
)
)
do c = ( // can move right?
player_x < width - 1 &&
(
map_at_player_offset(1, 0) is soft ||
(
map_at_player_offset(1, 0) == rock &&
player_x < width - 2 &&
map_at_player_offset(2, 0) == empty
)
)
)
if (!dead) (
if (
!b &&
!c &&
!(player_y > 0 && map_at_player_offset(0, -1) is soft) &&
!(player_y < height - 1 && map_at_player_offset(0, 1) is soft)
) (
do dead = 2
) elif (gems == allgems) (
do dead = -1
)
)
goto(drawmap)
switch (dead) (
case (-1) do printf("All %d Gems Collected! ", gems) break
case (2) do printf("Stuck! Final gems: %d", gems) break
case (1) do printf("Crushed! Final gems: %d", gems) break
default
do printf("%d / %d gems (past best: %d). WASD: move, R: reset, Q: quit.\n", gems, allgems, best)
do a = 0
switch (getc(stdin)) (
case (27)
if (getc(stdin) == 91) (
switch (getc(stdin)) (
case (65) do a = 1 break
case (68) do a = 2 break
case (66) do a = 3 break
case (67) do a = 4 break
)
)
break
case (239)
if (getc(stdin) == 156) (
switch (getc(stdin)) (
case (128) do a = 1 break
case (130) do a = 2 break
case (129) do a = 3 break
case (131) do a = 4 break
)
)
break
case ('w') do a = 1 break
case ('a') do a = 2 break
case ('s') do a = 3 break
case ('d') do a = 4 break
case ('r') do a = -1 break
case ('q') do a = -2 break
)
switch (a) (
case (-1) do dead = -2 do a = -1 do printf("\rReset.") break
case (-2) do dead = -3 do a = -1 do printf("\rQuit.") break
case (1) do a = 1 * (player_y > 0 && map_at_player_offset(0, -1) is soft) break
case (2) do a = 2 * b break
case (3) do a = 3 * (player_y < height - 1 && map_at_player_offset(0, 1) is soft) break
case (4) do a = 4 * c break
default do a = 0 break
)
if (a >= 0) (
reset_cursor
goto(gameloop with a)
)
break
)
)
function(drawmap) (
do printf(".-")
for (loop_drawx2, width)
do printf(".\n")
for (loop_drawy, height)
do printf("'-")
for (loop_drawx2, width)
do printf("'\n")
)
function(gravity) (
for (loop_gravx, width)
)
lambda(loop_gravx) (
do a = parameter
for (loop_gravy, height - 1)
)
lambda(loop_gravy) (
do b = mapcoord(a, parameter)
if (var(b) == rock && var(b + width) == empty) (
for (loop_gravy2, height - parameter - 1)
)
)
lambda(loop_gravy2) (
switch (var(b + width)) (
case (empty)
do var(b) = empty
do var(b + width) = rock
do b += width
break
case (player)
do dead = 1
break
)
)
lambda(loop_drawy) (
do a = height - parameter - 1
do printf("| ")
for (loop_drawx, width)
do printf("|\n")
)
lambda(loop_drawx) (
switch (map_at(width - parameter - 1, a)) (
case (player)
switch (dead) (
case (-1) put(T_ME, ":D") break
case (2) put(T_ME, ":S") break
case (1) put(T_ME, ":(") break
default put(T_ME, ":)") break
)
break
case (empty) do printf(" ") break
case (soil) do printf("~~ ") break
case (gem) put(T_GEM, "<>") break
case (rock) put(T_ROCK, "@@") break
case (wall) do printf("XX ") break
)
)
lambda(loop_drawx2) (
do printf("---")
)
lambda(loop_init) (
if (mapcoord(player_x, player_y) == mapindex(parameter)) (
do var(mapindex(parameter)) = player
do a = 1
) else (
do d = rand() % (parameter + a)
if (d < b) (
do var(mapindex(parameter)) = rock
do b --
) elif (d < b + c) (
do var(mapindex(parameter)) = wall
do c --
) elif (d < b + c + gems) (
do var(mapindex(parameter)) = gem
do gems --
) else (
do var(mapindex(parameter)) = soil
)
)
)
eof
vars.h
// Constants
// Short-lived scratch variables (various purposes)
// Variables
// Helpers
// Pseudo-enum
lang.h
// Headers
// Prototypes
int main(int v, char **s)
// Keywords
// Internal shorthand
Build as normal, e.g.:
gcc -std=c99 -Wall -Wextra -Weverything -pedantic -O3 -o bd bd.c
(from the zip, you can just run ./build.sh)
And run with:
./bd 20 20

Platform support
This makes a big assumption about memory layout. Specifically, it assumes that
it can access stack space and that stack space is allocated linearly, with fixed
deltas between each frame. This is true for most hardware environments, but not
true for targets such as emscripten (so no WebAssembly compilation for this
madness). It also assumes that the argv data will be close to the initial
stack frame (within 4096 bytes), which may be less portable.
Huh?
There are a few bizarre behaviours behind-the-scenes of this “language”:
- The only way to allocate memory is to recursively call the main function and abuse the stack
- The only way to iterate is to recursively call the main function
- Loops are defined as separate lambdas, not inline, and all functions and
lambdas actually behave like a large switch/case statement (which is why they
need unique numeric values defined in
vars.h) - All branching is handled by short-circuiting
&&and||operators - The macros in
lang.habstract all this mess into a pseudo-language - The “prototype” for
maininlang.his not a prototype at all!
Background
The inspiration for this project showed that it was possible to build a functional program using a single statement within a single function, combined with recursion. But it had a few drawbacks. Most notably: the whole stack memory was clobbered, including the return address for frames, so the function could never return. This meant every operation grew the stack until the program crashed. Also because it was for an obfuscated code contest, the syntax of the invented language was not very clear, and the program itself was quite basic due to the competition’s file size limit.
I wanted to build a more useable grammar around the same concepts, then use this
grammar for a more complex test program. I also wanted to avoid (or at least
reduce) the issue with stack space being exhausted. I achieved that by only
mutating the argc value on each stack frame. This leads to more initial
recursion since each call only provides space for a single value, but being able
to return from main means the stack grows very, very slowly once the program
is running. It will eventually run out of stack space, but you would need to
play a lot of levels to get there!
Language reference
The language defined in lang.h works like so:
run(allocSize, entryMethod)this defines the main entry point of the program and ensures there is enough available memory for the given number of variables (plus some extra space for internal variables). Internally it recursively callsmainuntil the stack space is large enough, seeds the random number generator, then invokes the requested entry method.function(id) ( code )this defines a function which can be marked as the entry function, or called later usinggoto. Function IDs should be within the mask value0xFFFF0000(i.e. must not set the lower 16 bits of data), but have no other restrictions.goto(function_id)jumps to the requested function, then returns to the next instruction when the function completes. Internally this translates to a recursivemaincall with the function ID inargc.goto(function_id with parameter)same asgotobut also sets a single parameter for the called function (this parameter defaults to 0 ifwithis not used). The value is available to the function asparameter(which is simplyargc & 0x0000FFFF; internally the function ID and parameter value are combined and passed asargc)lambda(id) ( code )likefunction, but adds code for looping using recursion. This is used withfor.for(lambda_id, iterations)convenience wrapper for looping; this invokes the given lambda ifiterationsis greater than 0. Each invocation of lambda will see the loop counter variable inparameter, counting down fromiterations-1to0. Becauselambdacontains code for recursing, it will automatically perform the full loop. Once the loop completes, any code below theforwill run as normal.dodenotes a section of regular code, akin to a statement. It will execute the statement if the program flow is not marked as stopped (e.g. by the use of abreakstatement).docan be chained, and behaves in a similar manner as semicolons (but as a prefix rather than suffix).var(id)returns a reference to the requested variable. The ID acts as an index in the memory list, where each entry occupies one stack frame (the exact process is described below).if (condition) ( code ) elif (condition) ( code ) else ( code )behaves like a standard if/else-if/else chain. Theelseis optional and there can be any number ofelifbranches. Internally this uses the short-circuting behaviour of&&and||, and exploits the fact that&&has higher precedence than||; with&&used for conditional execution and||used for “else”.ifstatements can be nested.switch (variable) ( case (value) commands break )behaves like a typical switch statement; there can be manycases, and fall-through is supported. There can also be adefault. Internally this uses a hidden variableSWITCHto store the given value, and a hidden marker variableACTIVEto detect whether execution is allowed to continue for a particular block, or has been stopped with abreak(this allows, for example, conditionalbreaking).switchstatements can be nested.eofappears at the end of the file. Internally this closes the generatedmainmethod, and writes the only semicolon in the source.
Variables are stored by overwriting argv parameters in early stack frames
(because other locations such as return addresses are not touched, it is safe
for function calls to return). The argv parameter of all invocations begins
set to a pointer to the first argv parameter, which stores the delta between
subsequent variables (this enables support for stacks which grow positively and
negatively). With these two pieces of information, it is possible to locate any
variable by index in the stack space. There are a few “hidden” variables stored
at negative offsets which are used for internal management (e.g. maintaining the
value of a switch statement). The macro depth can be used to find the
current variable count limit, which can be used (as in the boulder dash demo) to
allocate dynamic amounts of memory (the actual game loop will only begin once
the depth is enough for the requested grid size).
Internal value indices:
-3(ENV) containsargc(rightmost 8 bits),argv(next 12 bits, as an offset insizeof(void*)), andSTACK_FRAME_DELTA(the memory delta between stack frames, using all remaining bits).-2(ACTIVE) contains a flag for determining current execution state, as used by theswitch/case/breakstatements.-1(SWITCH) contains the currentswitchstatement’s value. This gets overwritten with nested statements, but as the matching outercasemust already have been found in such situations, it is not a problem.
Perhaps this language will take off and become the next Python? 🤞
Practical purposes
The IOCCC is a gem of crazy, creative things that are possible when the standard rules are thrown away. If you write software, please take a look at some of the other entries to their competitions.
In a similar vein, the Underhanded C Contest has lots of entries showing how subtle language features can cause unexpected (or at least non-obvious) behaviours.
These techniques have no place in professional (or even amateur) software, but studying and understanding them is a great way to get a deeper knowledge of how programs operate at lower levels, and shines a light on parts of the language which can be confusing and are potentially the source of bugs in real applications. Every language has gotchas, and giving ourselves a bit of time to play with them can give us insights which make us better at debugging, better at comprehending unfamiliar and unusual code, and all-round better software engineers.