Undefined Behaviour (also known as UB) occurs when you violate certain language rules. These rules include (but are not limited to): dereferencing a null pointer, signed integer overflow, accessing through a dangling reference, or accessing through an unaligned pointer.
When undefined behaviour occurs, the C and C++ standards do not place any restrictions on what your program might do. In other words, your program may crash, or continue execution, or call some seemingly unrelated piece of code, or print 42 and open xkcd in your web browser.
In practice, you may be able to reason about how your compiler will respond to UB, and in some cases compilers will guarantee that certain operations are well-defined, but for maximum portability you should aim to keep your programs UB-free.
Examples of UB
int x;
std::cout << x; // UB: x was used before being initialized
int *y = nullptr;
*y = 5; // UB: dereferencing null pointer
int arrayA[10];
for(int z : arrayA)
std::cout << z << ' '; // UB: array elements used when uninitialized
int *w = new int;
delete w;
*w = 5; // UB: object used after being destroyed
Avoiding UB
Undefined behaviour can be difficult to diagnose. Always compile with warnings enabled, but note that compilers can’t detect all problems at compile time. Tools like ASan, UBSan and Valgrind perform checks on your code at runtime and are good at catching invalid memory accesses, so are recommended for use during development.
For gcc
and clang
users, we recommend using
-Wall -Wextra -g -fsanitize=address,undefined
in your compilation flags during development. This enables compiler warnings, ASan, UBSan (and debugging symbols for nicer sanitizer error messages).