Discussion about this post

User's avatar
lcamtuf's avatar

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.

Vinnie Moscaritolo's avatar

Look up duff's device in c

1 more comment...

No posts

Ready for more?