Getting silly with C, part and((int*)-8)[3]

surprisetalk 26 points 5 comments August 21, 2026
blog.coredump.cx · View on Hacker News

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.

Semantic search powered by Rivestack pgvector
4,128 stories · 37,281 chunks indexed