C++ language is designed with efficiency in mind. But a side effect of this is that, for some actions, the programmer is left with the burden to make sure they are not invoked without meeting some pre-conditions. The runtime will not spend the CPU time and the memory to check that the pre-conditions are met and behave in a well-defined way if they are not. These cases are documented as undefined behavior: If such a precondition is not met, the code is allowed to have any effect.
Sometimes the effect of an undefined behavior is "expected" by the programmer. For example, writing outside the bounds of an array usually results in (in the best case) invoking a CPU trap, resulting in ending the program with segmentaion fault or similar or (worst case) overwriting some unrelated variable (memory corruption), leading to a hard to track bug.
But modern, aggressively-optimizing compilers, may lead to much stranger behavior. Consider the following code:
int foo() {
int i = 0;
while(i < 3) {
printf("i=%d\n", i);
++i;
}
}
int main() {
int v = foo();
printf("Val=%d\n", v);
}
Here, undefined behavior results from having a function, declared to return non-void, reaching the end without having a return statement. Programmers would normally expect the caller code to get an uninitialized int value "returned" by the function. But, with aggressive optimization, a possible (and reportedly encountered) behavior is to get an infinite loop - the compiler would "think" that, since there is no return statement after the loop, the loop is supposed to loop forever and thus the compiler would not "bother" to check for the looping condition.
For example, the above code, compiled with g++ version 14.2.0 and with optimization option -O1 results in an infinite loop printing i=0, i=1, i=2. The same code, with the same compiler, but with -O2 or -O3 results in a segmentation fault. And with -O0 it results in an illegal instruction.
Another undefined behavior case that is easy to confuse a programmer is the arithmetic overflow on signed integers.
While unsigned integer overflow is implementation-defined, usually defined as resulting in a wrap-around, the signed integer overflow is undefined behavior. Again, the compiler may do optimizations based on the assumption that signed integer arithmetic does not overflow, resulting in unexpected code.