Does integer overflow cause undefined behavior because of memory corruption?

Undefined behaviour is undefined. It may crash your program. It may do nothing at all. It may do exactly what you expected. It may summon nasal demons. It may delete all your files. The compiler is free to emit whatever code it pleases (or none at all) when it encounters undefined behaviour.

Any instance of undefined behaviour causes the entire program to be undefined - not just the operation that is undefined, so the compiler may do whatever it wants to any part of your program. Including time travel: Undefined behavior can result in time travel (among other things, but time travel is the funkiest).

There are many answers and blog posts about undefined behaviour, but the following are my favorites. I suggest reading them if you want to learn more about the topic.

  • A Guide to Undefined Behavior in C and C++, Part 1
  • What Every C Programmer Should Know About Undefined Behavior #1/3

You misunderstand the reason for undefined behavior. The reason is not memory corruption around the integer - it will always occupy the same size which integers occupy - but the underlying arithmetics.

Since signed integers are not required to be encoded in 2's complement, there can not be specific guidance on what is going to happen when they overflow. Different encoding or CPU behavior can cause different outcomes of overflow, including, for example, program kills due to traps.

And as with all undefined behavior, even if your hardware uses 2's complement for its arithmetic and has defined rules for overflow, compilers are not bound by them. For example, for a long time GCC optimized away any checks which would only come true in a 2's-complement environment. For instance, if (x > x + 1) f() is going to be removed from optimized code, as signed overflow is undefined behavior, meaning it never happens (from compiler's view, programs never contain code producing undefined behavior), meaning x can never be greater than x + 1.


The authors of the Standard left integer overflow undefined because some hardware platforms might trap in ways whose consequences could be unpredictable (possibly including random code execution and consequent memory corruption). Although two's-complement hardware with predictable silent-wraparound overflow handling was pretty much established as a standard by the time the C89 Standard was published (of the many reprogrammable-microcomputer architectures I've examined, zero use anything else) the authors of the Standard didn't want to prevent anyone from producing C implementations on older machines.

On implementations which implemented commonplace two's-complement silent-wraparound semantics, code like

int test(int x)
{
  int temp = (x==INT_MAX);
  if (x+1 <= 23) temp+=2;
  return temp;
}

would, 100% reliably, return 3 when passed a value of INT_MAX, since adding 1 to INT_MAX would yield INT_MIN, which is of course less than 23.

In the 1990s, compilers used the fact that integer overflow was undefined behavior, rather than being defined as two's-complement wrapping, to enable various optimizations which meant that the exact results of computations that overflowed would not be predictable, but aspects of behavior that didn't depend upon the exact results would stay on the rails. A 1990s compiler given the above code might likely treat it as though adding 1 to INT_MAX yielded a value numerically one larger than INT_MAX, thus causing the function to return 1 rather than 3, or it might behave like the older compilers, yielding 3. Note that in the above code, such treatment could save an instruction on many platforms, since (x+1 <= 23) would be equivalent to (x <= 22). A compiler may not be consistent in its choice of 1 or 3, but the generated code would not do anything other than yield one of those values.

Since then, however, it has become more fashionable for compilers to use the Standard's failure to impose any requirements on program behavior in case of integer overflow (a failure motivated by the existence of hardware where the consequences might be genuinely unpredictable) to justify having compilers launch code completely off the rails in case of overflow. A modern compiler could notice that the program will invoke Undefined Behavior if x==INT_MAX, and thus conclude that the function will never be passed that value. If the function is never passed that value, the comparison with INT_MAX can be omitted. If the above function were called from another translation unit with x==INT_MAX, it might thus return 0 or 2; if called from within the same translation unit, the effect might be even more bizarre since a compiler would extend its inferences about x back to the caller.

With regard to whether overflow would cause memory corruption, on some old hardware it might have. On older compilers running on modern hardware, it won't. On hyper-modern compilers, overflow negates the fabric of time and causality, so all bets are off. The overflow in the evaluation of x+1 could effectively corrupt the value of x that had been seen by the earlier comparison against INT_MAX, making it behave as though the value of x in memory had been corrupted. Further, such compiler behavior will often remove conditional logic that would have prevented other kinds of memory corruption, thus allowing arbitrary memory corruption to occur.


In addition to the esoteric optimization consequences, you've got to consider other issues even with the code you naively expect a non-optimizing compiler to generate.

  • Even if you know the architecture to be twos complement (or whatever), an overflowed operation might not set flags as expected, so a statement like if(a + b < 0) might take the wrong branch: given two large positive numbers, so when added together it overflows and the result, so the twos-complement purists claim, is negative, but the addition instruction may not actually set the negative flag)

  • A multi-step operation may have taken place in a wider register than sizeof(int), without being truncated at each step, and so an expression like (x << 5) >> 5 may not cut off the left five bits as you assume they would.

  • Multiply and divide operations may use a secondary register for extra bits in the product and dividend. If multiply "can't" overflow, the compiler is free to assume that the secondary register is zero (or -1 for negative products) and not reset it before dividing. So an expression like x * y / z may use a wider intermediate product than expected.

Some of these sound like extra accuracy, but it's extra accuracy that isn't expected, can't be predicted nor relied upon, and violates your mental model of "each operation accepts N-bit twos-complement operands and returns the least significant N bits of the result for the next operation"