Undefined Behavior & Safety

signed integer overflow as UB

Every fixed-size number has a largest value it can hold, the way a car odometer maxes out at 999999. Ask it to go one higher and on the hardware it usually 'wraps around' to the smallest value. You might reasonably expect C to do that too. For UNSIGNED integers it does, and the wrap is well-defined. But for SIGNED integers, going past the maximum is undefined behavior — and that surprising asymmetry trips up even experienced programmers.

Concretely: a 32-bit int can hold values up to INT_MAX, which is 0x7fffffff, that is 2147483647. Computing INT_MAX + 1 in C is undefined behavior. Not 'it wraps to INT_MIN' — that is what the typical two's-complement hardware does, but the C standard does not promise it, and the optimizer is free to assume the overflow never happens. By contrast, an unsigned int computing UINT_MAX + 1 is DEFINED to wrap to 0; unsigned arithmetic is modular arithmetic by the standard's own words. So the very same bit-level operation is undefined for one type and perfectly defined for the other. The reason the standard left signed overflow undefined is partly historical (machines once used different signed representations) and partly to license optimizations, such as assuming that a loop counter i only ever increases so the loop must terminate.

Why this matters: because the compiler may assume signed overflow never happens, the obvious overflow check is broken. Writing if (x + y < x) to detect overflow of two ints can be optimized away entirely, since 'x + y < x' implies overflow which 'cannot happen'. The correct approaches are to do the arithmetic or the check in an unsigned type, to compare against the limits BEFORE adding (if (x > INT_MAX - y) ...), or to use a compiler builtin such as __builtin_add_overflow that reports overflow safely. Never reason about signed overflow by imagining what the hardware does — in C, the hardware's wrap is not a guarantee you may rely on.

int x = INT_MAX; int y = x + 1; /* UB: do NOT assume this is INT_MIN */ /* broken check: may be optimized to 'if (false)' */ if (a + b < a) report_overflow(); /* safe check: compare before adding */ if (b > 0 && a > INT_MAX - b) report_overflow(); unsigned u = UINT_MAX; u = u + 1; /* DEFINED: wraps to 0 (modular) */

Signed overflow is UB and its naive check can be deleted; unsigned overflow is well-defined wraparound. Check signed limits before the operation.

Signed overflow = undefined behavior; unsigned overflow = defined wraparound (modulo 2^N). Conflating the two is a classic bug. The hardware wrapping for signed types is not a promise the standard makes, so the optimizer may ignore it.

Also called
signed overflow有號整數溢位