undefined-behavior-driven optimization
There is a subtle and consequential trick at the heart of optimizing C and C++ compilers, and being honest about it is more important than any single optimization. The C standard says that if a program executes undefined behavior, the standard imposes NO requirements at all. The optimizer takes this and runs with it: it assumes your program never has undefined behavior, and it uses that assumption to justify rewrites it otherwise could not. This is undefined-behavior-driven optimization.
First, three terms that are NOT the same. Undefined behavior (UB) means the standard places no requirements whatsoever — anything may happen, and crucially the compiler may assume it does not occur. Unspecified behavior means the standard allows several possibilities and the compiler picks one (e.g. the order of evaluation of function arguments). Implementation-defined behavior means the implementation must pick a documented choice (e.g. how many bits an int has). Only the first one licenses the optimizer to ASSUME. Here is how it bites. Because signed integer overflow is UB, the compiler may assume x + 1 > x is always true for a signed int x, and delete a check that guards against overflow. Because dereferencing a null pointer is UB, if your code does p->field and LATER checks if (p != NULL), the compiler may conclude p was non-null all along (since dereferencing it was UB if it were null) and DELETE the null check as redundant. Because reaching a certain branch would be UB, the compiler may treat that branch as unreachable and remove it. The optimizer is not malicious; it is reasoning soundly from a premise you violated.
It matters because this is why a program with UB can 'work' at -O0 and break at -O2, why a security check can silently vanish, and why a buggy program can be 'miscompiled' in ways that look like the compiler betrayed you. The honest framing: the compiler did not introduce the bug — UB did — but the optimizer's assumption is what made the latent bug visible and dangerous. The escape hatches are real and worth knowing: -fwrapv makes signed overflow wrap (defined) instead of UB; -fno-strict-aliasing turns off type-based aliasing assumptions; and UBSan (the Undefined Behavior Sanitizer, -fsanitize=undefined) instruments the program to catch UB at run time so you can find it instead of letting the optimizer exploit it. The real lesson is not 'turn off optimizations' — it is 'do not write undefined behavior'.
int *p = get(); int v = *p; // dereference: UB if p is NULL, so compiler assumes p != NULL if (p == NULL) // ...therefore this check is 'always false' and may be DELETED return -1; // this safety branch can silently disappear at -O2 return v;
Dereferencing p first makes the compiler assume p is non-null, so the later null check is deleted as dead — a real UB-driven 'miscompile' of buggy code.
Keep undefined, unspecified, and implementation-defined behavior strictly distinct: only UB lets the optimizer ASSUME it never happens (and thus delete checks). UB is not 'random' or 'platform-dependent' — it is a license to assume; -fwrapv, -fno-strict-aliasing, and UBSan are mitigations, but the fix is to not write UB.