94 Advanced The Hundred
The Division That Crashed
no zeros were harmed in this divide-by-zero
int scale(int value, int factor) {
if (factor == 0)
return 0; // the only divisor that can hurt us... right?
return value / factor;
}
int main(int argc, char** argv) {
int value = (argc > 1) ? std::atoi(argv[1]) : INT_MIN;
int factor = (argc > 2) ? std::atoi(argv[2]) : -1;
std::cout << "scale(" << value << ", " << factor << ") = " << scale(value, factor) << "\n";
}
Run it. The divisor is -1, not 0 — what does it print?
Answer
Nothing at all. Typical behavior (GCC 11.5 on x86-64) — the process is killed mid-statement:
$? is 136 — killed by signal 8, SIGFPE. Any other pair is fine (./demo 100 -1 prints
scale(100, -1) = -100), and the default pair dies identically at -O0 through -O3.
Why¶
int is lopsided: it runs from -2147483648 to 2147483647, so the result this division
calls for — positive 2147483648 — has no representation. That makes it signed overflow,
which is undefined behavior; but where entry 93 was the optimizer assuming it can't
happen, this is the CPU noticing that it did. x86-64 has one signed integer divide
instruction, idiv, and it traps on two inputs: a zero divisor, and the one quotient too
large to store — Linux delivers that trap as SIGFPE, so a program with no floating point
and no zero in it dies of a "floating point exception", its half-built output line still
unflushed in the stream buffer. INT_MIN % -1 hits the same instruction and dies the same
way; std::abs(INT_MIN) is undefined for the same missing-value reason (GCC 11.5 hands
back INT_MIN, still negative). -Wall -Wextra say nothing here; GCC's default
-Woverflow catches only the all-literal form (int x = INT_MIN / -1;), and if those
constants arrive through variables instead, -O2 folds the division at compile time and
cheerfully prints -2147483648 while -O0 crashes. -fsanitize=undefined names it:
"runtime error: division of -2147483648 by -1 cannot be represented in type 'int'".
The fix¶
Guard the second bad pair exactly like the first, or divide in a type that holds the answer:
if (factor == 0 || (value == INT_MIN && factor == -1))
return 0; // the pair that traps, handled like the pair that divides by zero
long long wide = static_cast<long long>(value) / factor; // 2147483648 fits in 64 bits
-fwrapv won't rescue you: it defines wrapping for +, - and *; idiv never got the memo.
Takeaway: exactly one integer division can overflow, and it doesn't wrap — it traps.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo — same crash at every -O level.