Skip to content
  • Undefined Behaviour & Common Pitfalls · C++

Undefined Behaviour & Common Pitfalls

LearnViaNotes @learnvianotes· 3 min read

Undefined behaviour (UB) means the standard places no requirements on what happens. The program may crash, work by accident, or be “optimised” into something surprising. Know the usual suspects.

The usual suspects

UBExampleTool that catches it
Out-of-bounds accessv[v.size()], arr[10] on int arr[10]ASan, .at(), -D_GLIBCXX_ASSERTIONS
Use after free / danglingReference into a vector that grew, returning &localASan
Uninitialised readint x; if (x) …-Wall (sometimes), MSan, Valgrind
Signed integer overflowINT_MAX + 1UBSan
Null dereference*p with p == nullptrCrash / UBSan
Data raceTwo threads write a non-atomic variableTSan
Shift out of range1 << 32 on a 32-bit int, negative shiftUBSan
Strict-aliasing violation*(int*)&someFloat-fstrict-aliasing warnings; use memcpy/bit_cast
Modifying a const objectconst_cast then write—
Missing return in a non-void functionFalling off the end-Wreturn-type (error in clang)

Sanitizers find UB for you

C++ — heap buffer overflow
#include <iostream>
#include <vector>
int main() {
    std::vector<int> v(3);
    int* data = v.data();
    std::cout << data[3] << '\n';     // one past the end → AddressSanitizer: heap-buffer-overflow
}
C++ — oversized shift
#include <iostream>
int main(int argc, char**) {
    int shift = 31 + argc;            // 32 at run time
    std::cout << (1 << shift) << '\n'; // UBSan: shift exponent 32 is too large for 32-bit type 'int'
}
Terminal
$ clang++ -std=c++20 -g -fsanitize=address,undefined main.cpp && ./a.out   # memory errors + UB
$ clang++ -std=c++20 -g -fsanitize=thread main.cpp && ./a.out              # data races

Classic pitfalls (not all UB, all bugs)

C++
#include <iostream>
#include <string>
#include <vector>

int main() {
    // 1. Integer division before conversion
    double avg = 7 / 2;                     // 3, not 3.5
    std::cout << avg << '\n';

    // 2. Assignment instead of comparison (clang warns; parentheses silence it)
    int x = 0;
    if ((x = 5)) std::cout << "always true\n";

    // 3. Unsigned wrap-around
    std::vector<int> empty;
    std::cout << (empty.size() - 1 > 0) << '\n';   // true: 18446744073709551615 > 0

    // 4. Copy in range-for
    std::vector<std::string> names{"a", "b"};
    for (auto n : names) n += "!";          // modifies copies
    std::cout << names[0] << '\n';

    // 5. Char arithmetic
    std::string s = "abc";
    std::cout << s[0] + s[1] << '\n';       // 195 — chars promote to int
}
Output
3
always true
1
a
195

What's the difference between undefined, unspecified and implementation-defined behaviour?

Why doesn't C++ just check array bounds?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report