This code assumes that signed integer overflow will wrap around from the largest representable value to the smallest representable value, and from there will eventually reach the value 0. However, the C standard only allows for wraparound of unsigned integers, not signed integers. Signed integer overflow is undefined behavior.
At lower optimization levels, this wraparound happens to be occurring, but because what you're doing is undefined behavior there is no guarantee of that. When you increase the optimization, the compiler takes advantage of the fact that signed integer overflow is not allowed by assuming it doesn't happen. So when it looks at the while loop, it takes that assumption into account and concludes that the loop doesn't end, and therefore generates an infinite loop.
Looking at the assembly code at -O3
confirms this:
main:
.LFB11:
.cfi_startproc
.p2align 4,,10
.p2align 3
.L2:
jmp .L2
.cfi_endproc
Here we see that i
and the condition are optimized away entirely and that an unconditional infinite loop is generated.
If you were to declare i
to have type unsigned int
, wraparound behavior is guaranteed and you'll get the result you expect.