A loop runs a signed int i from 0 while i is less than or equal to n, where n is an int taken from a message, and sums an array. The debug build terminates. At -O2 with n equal to INT_MAX the loop never terminates. Explain what the optimiser did and why the standard permits it.

A loop runs a signed int i from 0 while i is less than or equal to n, where n is an int taken from a message, and sums an array. The debug build terminates. At -O2 with n equal to INT_MAX the loop never terminates. Explain what the optimiser did and why the standard permits it.

Approach: Ask what the standard says about signed overflow, then what the optimiser is entitled to assume about a loop counter once that case has been excluded from the set of possible executions.

Signed integer overflow is undefined behaviour, so the compiler assumes i never overflows, which makes the exit test unreachable when n is INT_MAX, and it deletes the test and emits an infinite loop. The optimiser reasons that i is at most INT_MAX by construction, the loop only increments the induction variable, and the condition can therefore turn false only through an overflow the program promises never to perform, so the comparison is redundant and can be removed or replaced by a computed trip count. The debug build keeps the comparison and wraps, which is what the hardware does, so it terminates. Fix it by widening the counter to a type that cannot overflow at the bound, comparing with a strict less than against a size, and building the test targets with the undefined behaviour sanitiser and with signed overflow trapping on.

Follow-up: If the counter is changed to unsigned instead, what does the same loop do when n is zero and the array is empty?

Key concepts: undefined behaviour, signed overflow, induction variable, sanitiser.