how UB lets the optimizer delete checks
Suppose you add a careful safety check to your code: 'if this pointer is null, bail out before using it.' You feel safe. Then you build with optimizations on, and the check VANISHES from the program — the compiler removed the very line that was protecting you. This is not a compiler bug. It is the most surprising and most dangerous consequence of undefined behavior, and once you see why, you never forget it.
Here is the reasoning the optimizer follows, step by step. It sees you dereference a pointer p, then a few lines later it sees you test if (p == NULL). Dereferencing a null pointer is undefined behavior, and the optimizer is allowed to assume undefined behavior never happens. Therefore it reasons: 'since p was already dereferenced and the program is assumed correct, p CANNOT be null here, so the test if (p == NULL) must be false, so the branch inside it is dead code' — and it deletes the check. The mistake costs you twice: the protection is gone, AND the original dereference still runs on the bad pointer. The effect can run BACKWARDS in apparent time, too: because the whole program is assumed UB-free, the compiler may move or remove code that comes BEFORE the offending operation, which is why people describe UB as letting the optimizer 'time-travel'. An operation's consequences can appear earlier than the operation itself.
Why this matters: this is the mechanism that turns a 'harmless-looking' mistake into a genuine security hole. A signed-overflow check written as if (x + 1 < x) can be optimized to if (false) because signed overflow is undefined and thus 'never happens'; a length check can evaporate the same way. The practical lesson is not 'distrust the optimizer' but 'never write code containing undefined behavior in the first place', and to write your checks so they test the input BEFORE the dangerous operation, not after — and to use the appropriate well-defined construct (an unsigned type, a __builtin_*_overflow, an explicit range test) instead of relying on what the hardware happens to do.
void use(int *p) { int v = *p; /* if p is null this is UB */ if (p == NULL) /* optimizer: 'p was dereferenced, so it isn't null' */ return; /* -> this check may be DELETED entirely */ do_work(v); } /* Safe version: test BEFORE you dereference. */ void use_safe(int *p) { if (p == NULL) return; do_work(*p); }
Because the early dereference is UB if p is null, the compiler may assume p is non-null and remove the later null check.
The optimizer is not malicious and not buggy here — it is correctly exploiting the standard's promise that UB never happens. The fix is always to remove the UB, never to fight the optimizer. Validate inputs BEFORE the dangerous operation.