I was reviewing some code at work, and something jumped at me when reading some arithmetic:
static inline int64_t int32ToDecimal9(int32_t i) { return i * 1000000000; }
If you’ve had the good fortune of dealing with C or C++ for extended periods you would have recognized the problem. i * 1000000000
will be performed with 32-bit precision, which is almost certainly not what the programmer intended since it will lead to an overflow for any i
outside [-2, +2].
So far so good, a classic C footgun – we’ve all been there.
However code using this function was running, and working, which was more surprising. Take a moment to consider why before revealing the solution.
A direct compilation of int32ToDecimal9
will look something like this:
imul eax, edi, 1000000000
cdqe
First, perform 32-bit signed multiplication, then sign-extend the result into 64-bit.
However, signed overflow is undefined behavior, so an optimizing compiler might prefer to do the sign extension before as part of a mov
that it needed to do anyway, and then do the multiplication in 64 bits, thereby saving one instruction and “fixing” our bug in the process.
So we have a rare case of two C footguns cancelling each other out, rather than combining into an even bigger footgun as usual.