Obfuscated C

QR Code linking to this page

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

⬇️ download all

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

#include "lang.h"
#include "vars.h"

// Print helpers (with/without ANSI codes)
#ifdef NO_TERM_COLS
#  define put(f,m) do printf(m " ")
#  define reset_cursor
#else
#  define put(f,m) do printf(f m DEF " ")
#  define reset_cursor do printf("\x1b[%dF",height+5)
#endif

// 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
#define player 0
#define empty  1
#define soil   2
#define gem    3
#define rock   4
#define wall   5

#define T_ME   "\x1b[33;1m"
#define T_GEM  "\x1b[35;1m"
#define T_ROCK "\x1b[31m"
#define DEF    "\x1b[0m"

// Short-lived scratch variables (various purposes)
#define a var(0)
#define b var(1)
#define c var(2)
#define d var(3)

// Variables
#define size     var(4)
#define width    var(5)
#define height   var(6)
#define player_x var(7)
#define player_y var(8)
#define allgems  var(9)
#define gems     var(10)
#define best     var(11)
#define wincount var(12)
#define dead     var(13)
#define vars     14

// Helpers
#define mapindex(x)               (vars+(x))
#define mapcoord(x,y)             (vars+(y)*width+(x))
#define map_at(x,y)               var(mapcoord(x,y))
#define map_at_player_offset(x,y) map_at(player_x+(x),player_y+(y))
#define is
#define soft <rock

// Pseudo-enum
#define init        0x010000
#define gameloop    0x020000
#define drawmap     0x030000
#define gravity     0x040000
#define loop_init   0x100000
#define loop_drawy  0x200000
#define loop_drawx  0x300000
#define loop_drawx2 0x400000
#define loop_gravx  0x500000
#define loop_gravy  0x600000
#define loop_gravy2 0x700000
lang.h
// Headers
#include <stdio.h>  // printf, getc
#include <stdlib.h> // atoi, srand, rand, system
#include <time.h>   // time

// Prototypes
int main(int v, char **s)

// Keywords
#define run(allocSize,entryMethod){return 0&(!METHOD&&(v=(-STACK_OFFSET&0xFFF)<<8|v&0xFF)&&!main(entryMethod|allocSize+3,(char**)&v)\
ELIF(METHOD==entryMethod&&parameter)1|(!STACK_FRAME_DELTA&&1|(ENV|=STACK_OFFSET<<20)&&1|system("stty -icanon 2>/dev/null")\
ELIF(STACK_OFFSET/STACK_FRAME_DELTA==2)(INVOKE(void,(unsigned int),srand,((unsigned int)time(0)))))&&!main(v-1,s)
#define var(x)(*(int*)((size_t)s+(size_t)STACK_FRAME_DELTA*(size_t)((x)+3)))
#define case(x))&&(ACTIVE=1))|1&&(ACTIVE&&(ACTIVE==1||SWITCH==(x))&&(1
#define argv ((char**)((size_t)s+(size_t)(ENV>>8&0xFFF)))
#define depth (STACK_OFFSET/STACK_FRAME_DELTA-3)
#define function(x)ELIF(METHOD==x)FUNC_IMPL
#define lambda(x)ELIF(METHOD==x)LAMBDA_IMPL
#define switch(x)do SWITCH=x SWITCH_IMPL
#define for(y,x)if(x)(goto(y|x-1))
#define default ))|1&&(ACTIVE&&(1
#define elif(x)ELIF(x)IF_IMPL
#define goto(x)do main((x),s)
#define parameter (v&0xFFFF)
#define if(x)do(x)&&IF_IMPL
#define else(x)||IF_IMPL(x)
#define break )&&(ACTIVE=0
#define do )&&ACTIVE&&1|(
#define argc (ENV&0xFF)
#define with )|(
#define eof );}

// Internal shorthand
#define n(r,q)(*(r(**)q)((size_t)&s-(size_t)STACK_FRAME_DELTA))
#define LAMBDA_IMPL(x)FUNC_IMPL(x if(parameter)(goto(v-1)))
#define SWITCH_IMPL(x)do((ACTIVE=2)&&(0 x)))&&(ACTIVE=1
#define STACK_OFFSET ((int)(size_t)&v-(int)(size_t)s)
#define INVOKE(r,q,f,x)(n(void,q)=&f)&&n(int,q)x
#define FUNC_IMPL(x)((ACTIVE=1)x)|1&&(ACTIVE=1)
#define METHOD ((unsigned int)v&0xFFFF0000)
#define STACK_FRAME_DELTA (ENV>>20)
#define IF_IMPL(x)(1 x)
#define ELIF(x)||(x)&&
#define SWITCH var(-1)
#define ACTIVE var(-2)
#define ENV *(int*)s

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
A game of boulder dash in the terminal

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”:

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:

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:

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.