Undefined Behavior & Safety Terminology
Undefined Behavior (UB)
The standard does not define program behavior — the compiler can do anything. UB is not "a crash"; it is "anything can happen."
int a[3] = {1, 2, 3};
int x = a[5]; // UB: out-of-bounds access
// might return garbage, might crash, might "work fine" then blow up at the worst moment
// the compiler can even assume UB won't happen and optimize accordingly — eliminating relevant code pathsWhy Does UB Exist?
- Performance: No bounds checking, no overflow checking → faster code
- Portability: Different platforms behave differently; the standard does not mandate uniformity
- Optimization opportunities: The compiler can assume UB won't occur → aggressive optimizations
Implementation-Defined Behavior
The standard requires the behavior to be defined, but the specific behavior is determined by the compiler. The compiler must document its behavior.
sizeof(int); // implementation-defined (usually 4)
int i = -1;
unsigned u = i; // implementation-defined (usually 2^32 - 1)Unspecified Behavior
The standard specifies the range of allowed behaviors, but does not specify which one occurs. The compiler does not need to document it.
int f() { return 1; }
int g() { return 2; }
int x = f() + g(); // the calling order of f() and g() is unspecified
// f() might be called first, or g() might be called firstCommon UB List
Signed Overflow
int x = INT_MAX;
x++; // UB! Signed integer overflow is UB
unsigned y = UINT_MAX;
y++; // OK! Unsigned integer overflow is well-defined (modular 2^N)Null Dereference
int* p = nullptr;
*p = 42; // UBUse-After-Free
int* p = new int(42);
delete p;
*p = 0; // UB: the memory pointed to by p has been freedBuffer Overflow
int arr[10];
arr[10] = 42; // UB: index out of bounds (valid indices are 0–9)Dangling Reference
int& bad() {
int x = 42;
return x; // returns a reference to a local variable — UB
}Strict Aliasing Violation
float f = 3.14f;
int i = *reinterpret_cast<int*>(&f); // UB! Violates strict aliasing ruleUse of Uninitialized Variable
int x;
int y = x + 1; // UB: x is uninitializedNasal Demons
A classic metaphor for UB — "anything can happen, including demons flying out of your nose." This is a joke from the comp.lang.c era, but it precisely describes the danger of UB.
Sanitizers
Compiler tools that detect UB at runtime:
# AddressSanitizer (ASan): detects memory errors
g++ -fsanitize=address main.cpp && ./a.out
# ThreadSanitizer (TSan): detects data races
g++ -fsanitize=thread main.cpp && ./a.out
# UndefinedBehaviorSanitizer (UBSan): detects UB
g++ -fsanitize=undefined main.cpp && ./a.out
# MemorySanitizer (MSan): detects use of uninitialized memory
clang++ -fsanitize=memory main.cpp && ./a.outContracts (C++26 Proposal)
Explicit checks in function preconditions/postconditions — turning implicit UB into explicit runtime errors:
int divide(int a, int b)
pre (b != 0) // precondition: b is not zero
post (r: r * b == a) // postcondition: result is correct
{
return a / b;
}If contracts are violated, the default behavior is std::terminate — much safer than UB.