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

BugSafer alternative
Out-of-bounds v[i]Range-for, algorithms, .at(), hardened library modes
Dangling references, pointers, views and iteratorsReturn by value; don't keep references into containers that may grow
Use after free, double delete, leaksNo naked new/delete: containers and smart pointers
Uninitialised variablesAlways initialise; use {}
Signed integer overflowWider types; check before computing
Data racesMutexes, atomics, no shared mutable state (lesson 47)
Bad casts, mismatched typesNamed 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:

C++
#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';
}
With AddressSanitizer
SUMMARY: AddressSanitizer: heap-buffer-overflow main.cpp:8 in main

A dangling reference into a vector that reallocated — a classic C++ bug:

C++
#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';
}
With AddressSanitizer
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:

C++
#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";
}
With UndefinedBehaviorSanitizer
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:

LibraryOption
libstdc++ (GCC)-D_GLIBCXX_ASSERTIONS
libc++ (Clang, LLVM 18+)-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
MSVCDebug builds check iterators by default

Lifetime safety habits

  • Don't store references, pointers, iterators, string_views or spans 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:

Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
Ana