Getting silly with C, part and((int*)-8)[3]
surprisetalk
26 points
5 comments
August 21, 2026
Related Discussions
Found 5 related stories in 42.8ms across 4,128 title embeddings via pgvector HNSW
- Using GCC's Nested Functions with Wide Pointers and No Trampolines II uecker · 92 pts · August 15, 2026 · 55% similar
- Tumble Forth – from assembly to OS with C compiler (2023) vicek22 · 70 pts · August 21, 2026 · 48% similar
- An ambiguity in c89 which will never be fixed runningmike · 15 pts · August 10, 2026 · 48% similar
- The Integer hactually · 19 pts · August 19, 2026 · 46% similar
- Writing a (valid) C program without main() birdculture · 20 pts · July 25, 2026 · 44% similar
Discussion Highlights (2 comments)
pkasting
The common theme of this whole series of articles is basically "GCC extensions allow abominations".
nneonneo
Some of the previous editions came with explainers, but this one doesn't, so here's what's going on: `void main() void;`: Despite sort of looking like an independent declaration (which threw me for a loop initially), this is actually an old-style C function definition in disguise. This style of function definition looks like `int foo(a, b, c) int a; int b; int c; { return a * b + c; }`. Without the misleading spacing, the blog code becomes `void main() void; void; { puts("hello world"); }`, in which it is clear that this is an old-style function definition with two useless declarations attached (void;). This one is both standard and free of UB. `int typedef[[]]$;`: This is a combination of a few features: (1) $ is allowed as a legal identifier by some C compilers, (2) [[]] is an empty C23 attribute specifier sequence (basically a standardized form of __attribute__), (3) && is both the familiar binary operator, and (as a GCC extension) a unary operator that takes the address of a label, and (4) `typedef` doesn't have to be the first keyword in a typedef definition. So, `int typedef[[]]$;` is simply `typedef int $;` - defining $ as a type alias for int. `int main($[[]]$)` says that `main` takes one argument - an `int` (typedef'd $) called `$`. `[[]]$:&&$&&$&&puts("hello world");` defines a label called `$`, then breaks down as `&&$ && $ && puts(...)` - take the address of the label `$`, logical AND the argument `$`, logical AND the result of `puts`. This one uses non-standard features ($ identifier, unary &&), but is free of UB. `goto *puts("Hello world"), puts("Goodbye world"), exit;`: this is a computed goto statement that evaluates the comma-expression `puts("Hello world"), puts("Goodbye world"), exit`, which calls `puts` twice and produces the address of `exit` (implicit function-to-function-pointer conversion), using that as the goto target. So, in effect, it's two free calls to puts followed by `goto *&exit`. Note that this is UB, as it effectively tail-calls `exit(int)` with no arguments. This one uses the non-standard computed goto feature, and has UB, although it will probably work in practice. `printf("Let's count: %d %d %d %d\n", i++, var[42], i++, i++);`: As far as I can tell, this is a bit of compiler weirdness (and is extremely UB). `var` is an array of empty unions, and takes up no space at all (although it will still have a defined address in the binary). `var[42]` is an empty union object and, at least on x86-64, takes up no space in a variadic argument list; it's therefore skipped over entirely when building the arguments to `printf`. This means that `printf` actually gets one fewer argument than expected; the missing argument just-so-happens to be a zero on the Compiler Explorer demo. Because this one is very UB (both because of the unsequenced `i++` and the incorrect argument list), expect the result to vary depending on the mood your compiler is in. I believe empty unions are non-standard, and this one is definitely UB; the result is very compiler- and system-dependent. `(my_type)2 + 2`: `my_type` is a pointer to an anonymous empty union with `sizeof` 0, so `(my_type)2` effectively treats 2 as the base address to an array of 0-sized unions. Adding a number to a pointer is equivalent to taking an array address (i.e. `p + x == &p[x]`). Since the size of each element is zero, asking for the address of element 2 is still going to give the base address (2). Compare this with, for example, `(int *)2 + 2` (gives 10, assuming `int` is four bytes in size). Note though that there is again UB here, because the argument to `printf` is a pointer, but it's being printed using the `%d` specifier. This again uses the non-standard empty union, and it also invokes UB.