93 Advanced The Hundred
The Overflow That Never Happens
one program, one input, two answers
bool bigger(int x) {
int next = x + 1;
return next > x; // adding one makes it bigger... right?
}
int main(int argc, char** argv) {
int x = (argc > 1) ? std::atoi(argv[1]) : INT_MAX;
std::cout << "x = " << x << "\n";
std::cout << "next > x = " << std::boolalpha << bigger(x) << "\n";
}
Compile at -O0, then at -O2. What does each build print?
Answer
They disagree. Typical output (GCC 11 on x86-64):
Why¶
INT_MAX + 1 is signed integer overflow, and that is undefined behavior — the
compiler may assume it never happens. At -O0 the machine add really executes, wraps
to INT_MIN, and the comparison is honestly false. At -O2 the optimizer reasons
"since x + 1 can't overflow, next > x is always true" and compiles the whole function
to return true. Neither build is wrong: with UB, both are conforming — and neither
-Wall -Wextra nor even -Wstrict-overflow=5 says a word on GCC 11. It's not only the
optimizer, either: write it as the one-liner return x + 1 > x; and GCC folds it to
true even at -O0. Unsigned overflow, by contrast, is fully defined: it wraps
modulo 2^N.
The fix¶
Ask the question without doing the arithmetic — or widen to a type the sum still fits in:
Two flags help you hunt for the ones you missed. -fwrapv defines signed overflow as
wrapping, so the original prints false at -O0 and -O2 alike; -fsanitize=undefined
reports it as it executes: "runtime error: signed integer overflow: 2147483647 + 1 cannot
be represented in type 'int'".
Takeaway: undefined behavior doesn't mean "it wraps" — it means the optimizer is allowed to assume it never happens, and it will.
Try it: g++ -std=c++17 -O0 main.cpp -o demo && ./demo, then again with -O2