Weekend projects: getting silly with C
C is beautiful yet full of horrors. Here's to uncovering its hidden depths.
For all its warts, the C language is a marvelous thing. It is remarkably simple, yet somehow expressive enough to allow entire operating systems to be written with ease. Just as curiously, its terse, minimalistic syntax became the way to structure code — copied by nearly all of its mainstream successors, from Java to Go.
Among geeks, the syntax can also be credited for the emergence of code obfuscation as an art form. The IOCCC contest is perhaps the best-known outlet for this craft; a typical IOCCC submission looks like this:
#define q [v+a] #define c b[1] #define O 1 q #define o 0 q #define r(v,a\ )v<0&&( v*=-1, a*=-1); #define p(v,m, s,w)*c==*#v?2 q\ <m?(c++ ,d=1,3 q=0,5 q=m,main\ (a+3,b) ,o=o*s q,O=O* w q):0: static d,v[99 ];main (int a, char**b ){d=7; if(*c?! (p(+,3 ,4 q+O* 3,4)p( -,(o?3 :(O=1,6 )),4 q -O*3,4) p(*,4,3 ,4)p(/ ,5,4,3) p((),d, 0+3,0+ 04)*c== ')'?2 q <02?(c ++,0):0 :(o=012 *o+*c- '0',c++ ,O=1)): 2 q?3- 2:printf( "%d/%d" "\n",o ,O))return 1;d=a,r (o,d)r (O,d)3 q =o<O?(4 q=o,O) :(4 q=O, o);r(d, o)a+=3;O? 1:(O=1,2 q=1);while (2 q=o%1 q)a++;v[d]/=O;d[ v+1]/=O;return main(d,b);}
There’s plenty to admire about the winning IOCCC entries, but they’re usually not fun to study: they tend to rely on confusing preprocessor macros, nonsensical formatting, unhelpful variable names, and simple logic encoded as obtuse arithmetic expressions that need to be reverse-engineered back into normal code.
This is unfortunate; the C language can easily confound seasoned developers without being hard to read. To illustrate, consider the humble switch (…) statement:
switch (i) { case 0: puts("i = 0"); break; case 1: puts("i = 1"); break; case 2: puts("i = 2"); break; }
There are very few C developers who realize that switch (…) is no different from if (…) or for (…) in that it doesn’t actually need curly brackets. This will compile just fine:
switch (i) case 1: puts("i = 1");
Such switch (…) notation is unheard-of and never encountered in real life simply because it defeats the purpose: without angle brackets, you can only have one statement riding on the coattails. In other words, this will not work:
switch (i) case 1: puts("i = 1"); case 2: puts("i = 2"); ← ERROR: no longer in switch (...)
Oh well!
On a seemingly unrelated note, let’s ponder the actual mechanics of switch (…): in essence, it’s a glorified goto. It jumps to the matching case label, but it doesn’t care about what’s going on in between the curly brackets; it’s a code block like any other:
switch (i) { int a = 123; puts("This code is unreachable!"); default: printf("a = %d\n", a); }
The above example should print the value of a, but it won’t be initialized to 123 (in fact, you technically get undefined behavior). If you don’t believe me, you can try it out here.
Just as unexpectedly, case labels don’t really need to appear top-level in their associated switch (…) block. In particular, this code works perfectly fine (link):
switch (i) { if (0) case 0: puts("i = 0"); if (0) case 1: puts("i = 1"); if (0) case 2: puts("i = 2"); }
Note that in this example, you don’t need break statements to avoid fallthrough; the code unconditionally jumps to the appropriate case label, skipping over the preceding if (0); but once the relevant puts(…) is executed, all subsequent calls are gated behind the remaining, perpetually-false if (0) conditionals.
But wait, there’s more! Recall that if can be chained with else — and that syntactically, the entire blob functions as a single top-level statement:
if (one_thing) do_one_thing; else do_another_thing;
So… without further ado, I present you the following curly-bracket-free monstrosity that combines all the quirks we discussed so far (link):
#include <stdio.h> int main() { int i = 1; switch (i) if (0) case 0: puts("i = 0"); else if (0) case 1 ... 10: puts("i = 1 ... 10"); else if (0) case 11: puts("i = 11"); else if (0) default: puts("i = something else"); return 0; }
And who needs switch (…), anyway? The && operator is a longstanding GNU extension that lets you get an address of a label; you can then goto to that address. Equipped with this knowledge, you can make your own switch (…) —with blackjack, et cetera (link):
#include <stdio.h> int main() { int i = 1; goto *(void*[]){ &&case_0, &&case_1, &&case_2 }[i]; if (0) case_0: puts("i = 0"); if (0) case_1: puts("i = 1"); if (0) case_2: puts("i = 2"); return 0; }
Heck, here’s another fantastic use for &&: why bother with for (…) if you can use labels to implement loops directly within variable declarations? Check this out (link):
#include <stdio.h> int main() { /* Iterate from i = 0 to i = 5: */ int i = (i = 0) & ({_: 0;}) | printf("i = %d\n", i) * (++i > 5) ?: ({goto *&&_; 0;}); return 0; }
This last snippet is probably not UB-safe and is GCC-specific. But the point stands: you can write completely alien and befuddling code in C without making it unreadable.
If you liked this article, please subscribe! Unlike most other social media, Substack is not a walled garden and not an addictive doomscrolling experience. It’s just a way to stay in touch with the writers you like.
For a thematic catalog of posts on this site, click here.
As silly as it is nowadays, switch just being a glorified goto was used seriously for manual loop unrolling. Let me introduce Duff's Device: https://en.wikipedia.org/wiki/Duff%27s_device
Nice. Now I'm curious what other creative (ab)uses of C the machines will find once they got trained properly.