Undefined Behaviour and Memory Safety
C++ trusts the programmer. Some mistakes — reading past the end of a vector, using a dangling reference, overflowing a signed integer — have undefined behaviour: anything may happen, and optimisers assume it never does. Modern C++ gives you the tools to avoid these bugs, and sanitizers find the ones that slip through.
Common sources of undefined behaviour
| Bug | Safer alternative |
|---|---|
Out-of-bounds v[i] | Range-for, algorithms, .at(), hardened library modes |
| Dangling references, pointers, views and iterators | Return by value; don't keep references into containers that may grow |
| Use after free, double delete, leaks | No naked new/delete: containers and smart pointers |
| Uninitialised variables | Always initialise; use {} |
| Signed integer overflow | Wider types; check before computing |
| Data races | Mutexes, atomics, no shared mutable state (lesson 47) |
| Bad casts, mismatched types | Named casts; avoid reinterpret_cast |
AddressSanitizer
Compile with -fsanitize=address while testing and memory errors are reported the moment they happen, with the exact line. These examples show the program's output and the final SUMMARY line of each report:
#include <iostream> #include <vector> int main() { std::vector<int> scores{70, 85, 90}; int total = 0; for (std::size_t i = 0; i <= scores.size(); ++i) { // <= reads one element too far total += scores[i]; } std::cout << total << '\n'; }
SUMMARY: AddressSanitizer: heap-buffer-overflow main.cpp:8 in main
A dangling reference into a vector that reallocated — a classic C++ bug:
#include <iostream> #include <string> #include <vector> int main() { std::vector<std::string> names{"Ana"}; const std::string& first = names[0]; // reference into the vector's storage names.push_back("Ben"); // may reallocate: 'first' now dangles names.push_back("Chloe"); std::cout << first << '\n'; }
SUMMARY: AddressSanitizer: heap-use-after-free string:1881 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::__is_long[abi:ne190102]() const
Here the summary names the place where freed memory was finally touched — inside the standard library's string code, while printing. The full report's stack trace leads back to line 10 of main.cpp, and also shows where the memory was freed (the push_back that reallocated).
UndefinedBehaviorSanitizer
-fsanitize=undefined catches arithmetic and type errors such as signed overflow:
#include <climits> #include <iostream> int main() { int big = INT_MAX; volatile int result = big + 1; // signed overflow (void) result; std::cout << "the program kept running\n"; }
the program kept running main.cpp:6:31: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
Build your tests with -fsanitize=address,undefined routinely (lesson 49 shows a CMake option for it).
Hardened standard libraries
Standard libraries can check preconditions such as indexes at run time, turning silent memory corruption into an immediate, clear failure. Enable it in debug builds, and consider it for release builds too — the cost is small:
| Library | Option |
|---|---|
| libstdc++ (GCC) | -D_GLIBCXX_ASSERTIONS |
| libc++ (Clang, LLVM 18+) | -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST |
| MSVC | Debug builds check iterators by default |
Lifetime safety habits
- Don't store references, pointers, iterators,
string_views orspans into containers that may change. - Don't return references or views to local variables or temporaries.
- Be careful with lambdas that capture by reference and run later (callbacks, threads).
- Prefer values: copying small objects is cheap and always safe.
Static analysis
Tools that read your code without running it catch many problems earlier: compiler warnings (-Wall -Wextra -Werror), clang-tidy (with checks based on the C++ Core Guidelines), Clang's -Wlifetime-style analyses, cppcheck, and commercial analysers. Run them in CI.
The bigger picture
Memory-safety bugs are a major source of security vulnerabilities, and governments and large companies now encourage memory-safe practices. The C++ community's response includes the Core Guidelines, hardened libraries, sanitizers, safety profiles proposed for future standards, and C++26 work on contracts and safer defaults. Writing modern C++ — values, RAII, containers, smart pointers, spans and views used carefully — already eliminates the large majority of these bugs.
Try it yourself
Fix the dangling-reference program above so that it's correct, then run it under AddressSanitizer to confirm it reports nothing. One fix is to copy the string instead of referring to it:
Ana