Getting silly with C, part ~(~1<<1)
It's vibe coding. Relax, everyone is doing it.
In the two previous installments of our introductory series on the C programming language, we talked about control flow, variables, and types. For example, we explained how to display strings on the screen:
int typedef puts(char* puts; char* puts; char* puts); int main() { puts(puts); puts("Welcome to my humble abode!"); }
In today’s episode, we’ll share a couple of additional, time-saving tips for seasoned pros.
🙘 Tip 1 🙚
Experienced software engineers often find it necessary to comment out large swatches of code. Unfortunately, the C language doesn’t support nested comments, so the process of commenting out previously-commented code is a chore a chore. But did you know that the GNU C Compiler allows you to harness the power of assembly language to effortlessly work around this shortcoming? We illustrate the technique below (demo):
#include <stdio.h> int main() { puts("Hello world"); asm("/*"); /* Nested comment */ for (int i = 0; i < 10; i++) puts("I LIKE PANCAKES!"); asm("*/"); puts("Goodbye world"); }
🙘 Tip 2 🙚
Although the C language provides support for a wide range of numbers including 0 and 1, programmers should be aware of a floating-point bug that affects certain Intel CPUs. The affected processors evaluate 0.999… as if it were exactly equal to 1.
To test for the issue, developers can rely on a recent GNU C extension called [[gnu::assume(…)]] that allows them to specify the expected behavior at compile time. Be aware that systems that fail the check may exhibit a range of symptoms, including fisherman’s ulcers, tail recursion, and worse (demo):
#include <stdio.h> int main() { static int i; if (++i > 10) return 0; printf("hello cruel world %d\n", i); [[gnu::assume(0.99999999999999999 < 1)]]; }
🙘 Tip 3 🙚
Arithmetic operations in C are famously cumbersome. Thankfully, the latest version of the standard introduces unary right-worm (-~) and left-worm (~-) operators. These operators allow integer values to be incremented or decremented without having to resort to the clunky “+ 1” or “- 1” notation (demo):
#include <stdio.h> int main(void) { printf(" 42 + 1 = %d\n", -~42); /* Right worm (+ 1) */ printf(" 42 - 1 = %d\n", ~-42); /* Left worm (- 1) */ printf(" 42 + 3 = %d\n", -~-~-~42); /* Triple right worm (+ 3) */ printf(" 42 - 5 = %d\n", ~-~-~-~-~-42); /* Quintiple left worm (- 5) */ #define 🪱 -~ #define 🐍 ~- printf(" 42 - 3 + 2 = %d\n", 🪱 🪱 🐍 🐍 🐍 42); /* Works with negative numbers too! */ printf("-42 + 5 = %d\n", 🪱 🪱 🪱 🪱 🪱 -42); }
🙘 Tip 4 🙚
Because of the limited-size data types used to store numerical values, computer algebra is necessarily approximate. However, when the language’s built-in approximations are insufficient, the C language standard allows you to adjust the value of any integer of your choice (demo):
int main() { int 𝟧 = 4; printf("5 = %d\n", 𝟧); }
🙘 Tip 5 🙚
It is a common misconception that strings in C need to be enclosed in quotation marks. This is a wasteful, legacy approach; the following snippet shows a better way (demo):
#include <stdio.h> #define puts(x...) puts(%:x) int main() { puts(Hello, world!); }
Click here for part four — and if you like this publication, please subscribe!


Actual explanations, in case you've come here to actually learn something "useful":
1) The assembly comment case: this should be pretty straightforward, just unexpected. GCC uses several more or less standalone compilation stages, one of which involves translating preprocessed C to assembly. If you inject comments into this stage, they will comment out some of the generated assembly code, with potentially hilarious effects.
2) Tail recursion: this is a funny example of aggressive optimization. [[gnu::assume(...)]] is a GNU extension that allows you to tell the compiler "if the execution ever gets to this point, you can safely assume the following for optimization purposes". A sane use would be something like [[gnu::assume(x > 2)]] if x is indeed always x > 2, as that may allow the compiler to optimize the code better. But here, we're saying that 0.99999999999999999 < 1; due to rounding, this is actually translated to 1 < 1. Since this is impossible, the compiler decides that it reached some paradoxical code path and stops generating code. The function main() ends abruptly, causing the execution to fall through; the next object is actually the internal _start function, which does some housekeeping and then calls main() again, so we have an endless loop.
3) Worms: on a hardware level, signed numbers are stored as two's complement, basically an bit-flipped version of the positive number (with sign indicated by the MSB), offset by -1 because there's no need for negative zero. So, if you take a signed int equal to 0 and flip all the bits (~0), you get -1, not -0. Similarly, ~1 is the same as -2, ~2 is -3; for any bit-flipped positive n, you get a negative integer -(n+1). If you then flip the sign with an unary - operator (-~n), the result is just a positive n + 1. If you do this in the other order (~-), you first get a negative int -n, and then invert it to a positive complement, which is n - 1.
4) Value adjustment: Unicode homoglyph.
5) Unquoted strings: three things. First, %: is an ancient but still-supported digraph for #. Second, the # operator in GCC macro bodies converts macro parameter to string literal, which can be useful for assert-style macros that want to throw a descriptive error:
#define my_assert(x) do { if (!(x)) puts("Assertion failed: " #x); abort(); } while (0)
That said, this wouldn't work with "Hello, world!" because without quotes, we're actually passing two parameters to a macro: "Hello" and "world!". This is where the "..." part comes in. In GCC, macros can have a variable number of parameters that can be then pasted as a whole by referencing the preceding name.
Look up duff's device in c