Part V — Security-focused C
A different angle on everything so far: not building features, but not shooting yourself in the foot. Undefined behavior is the one topic every C programmer needs memorized rather than just referenced — the rest of this Part (safe string handling, hardening flags, fuzzing, static analysis) is the tooling that catches what memorization alone misses.
28. Undefined behavior: the list every C programmer must memorize
Undefined behavior (UB) means the standard places no requirement on what happens — not "it crashes," but "the compiler is allowed to assume it never happens and optimize accordingly," which is often more surprising than a crash.
| UB | Example | Why it matters |
|---|---|---|
| Signed integer overflow | INT_MAX + 1 |
Compiler may assume x + 1 > x always and eliminate an overflow check you wrote |
| Out-of-bounds access | arr[10] on a 10-element array (valid indices 0–9) |
No bounds checking exists; you silently corrupt adjacent memory |
| Use of uninitialized value | int x; printf("%d", x); |
Value is indeterminate; sanitizers catch it, plain runs may "work" until they don't |
| NULL pointer dereference | *(int*)NULL |
Often segfaults, but the compiler may also assume it's unreachable and delete surrounding code |
| Use-after-free / dangling pointer | Section 10.2 | Silent corruption, sometimes exploitable |
| Data race | Section 20.1 | No defined result; can tear values, reorder observably |
Modifying a const object |
casting away const and writing |
May write to read-only memory (segfault) or silently do nothing, depending on placement |
Missing return in non-void function |
falls off the end | Caller gets a garbage value |
| Strict aliasing violation | reading a float* through an int* |
Compiler may reorder/omit the access assuming the pointers can't alias |
| Shift by ≥ the type's width | 1 << 32 on a 32-bit int |
Undefined behavior, not "0" as many assume |
War story. The signed-overflow row above is not theoretical: a real
class of Linux kernel bugs came from code like if (ptr + len < ptr) to
detect pointer-arithmetic overflow — because pointer overflow (and
signed overflow generally) is UB, compilers began optimizing this
entire check away, reasoning "overflow can't happen, so this comparison
is always false." Code that had defended against overflow for years
silently stopped defending against it after a compiler upgrade, with no
warning. The fix across the industry was to switch to
__builtin_add_overflow (GCC/Clang builtin) or unsigned arithmetic with
explicit wraparound semantics, both of which are defined behavior the
compiler cannot optimize away.
#include <stdio.h>
int safe_add(int a, int b, int *result) {
return __builtin_add_overflow(a, b, result); // returns 1 if overflow occurred, defined behavior
}
int main(void) {
int result;
if (safe_add(2147483647, 1, &result)) {
printf("overflow detected, not UB\n");
}
return 0;
}