Skip to content

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):

$ g++ -std=c++17 -O0 main.cpp -o demo && ./demo
x        = 2147483647
next > x = false

$ g++ -std=c++17 -O2 main.cpp -o demo && ./demo
x        = 2147483647
next > x = true

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:

bool bigger(int x) {
    return x < INT_MAX;   // false at every -O level, and defined
}

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

Open in Compiler Explorer ↗ Quiz this entry