Skip to content
C++

C++ Runtime Sanitizers

ASan, UBSan, TSan, MSan, and LSan — what each catches, runtime overhead, and how to layer them for maximum coverage.

The golden rule of sanitizers

Enable -fsanitize=address,undefined on every debug and CI build. These two together catch the vast majority of C++ bugs at ~2-3× overhead — a cheap price for correctness.

ASan + UBSan — default dev build, every day.
TSan — multithreaded code, separate CI job.
MSan — Clang only, add when tracking uninit reads.

Note: ASan and TSan are mutually exclusive — run in separate builds. MSan requires rebuilding all dependencies.

Comparison matrix

PropertyASanUBSanTSanMSanLSan
Compiler flagaddressundefinedthreadmemoryleak
CPU overhead~2×~1.2×~5-15×~3×~1×
Memory overhead~2×~1×~5-10×~3×~1.1×
GCC
Clang
MSVCPartial (/RTC1)
ASan
AddressSanitizer
-fsanitize=address

The most important sanitizer. Catches heap/stack/global buffer overflows, use-after-free, and use-after-return.

What it catches

  • Heap buffer overflow / underflow
  • Stack buffer overflow
  • Global buffer overflow
  • Use-after-free (dangling pointer reads/writes)
  • Use-after-return
  • Use-after-scope
  • Double free / invalid free

Does NOT catch

  • Uninitialized reads (use MSan)
  • Data races (use TSan)
  • Integer overflow (use UBSan)
UBSan
UndefinedBehaviorSanitizer
-fsanitize=undefined

Catches C++ undefined behavior at runtime: signed overflow, null deref, bad casts, misaligned access.

What it catches

  • Signed integer overflow
  • Null pointer dereference
  • Invalid downcast (dynamic_cast without check)
  • Misaligned pointer access
  • Shift-count overflow
  • Division by zero
  • Returning from non-void function
  • VLA bounds violation

Does NOT catch

  • Buffer overflows (use ASan)
  • Data races (use TSan)
  • Uninitialized reads (use MSan)
TSan
ThreadSanitizer
-fsanitize=thread

Detects data races at runtime. The only practical tool for finding concurrent memory bugs without heavy analysis.

What it catches

  • Data races (concurrent read/write without synchronization)
  • Lock order inversions (potential deadlock)
  • Misuse of std::mutex / std::atomic
  • Use of uninitialized mutex

Does NOT catch

  • Buffer overflows (use ASan)
  • UB (use UBSan)
  • All logical race conditions
MSan
MemorySanitizer
-fsanitize=memory

Catches reads of uninitialized memory — the silent source of non-deterministic bugs. Clang-only.

What it catches

  • Read of uninitialized stack/heap memory
  • Conditional branches on uninitialized values
  • Passing uninitialized data to system calls

Does NOT catch

  • Buffer overflows (use ASan)
  • Data races (use TSan)
  • UB (use UBSan)
LSan
LeakSanitizer
-fsanitize=leak

Memory leak detector — bundled with ASan (enabled by default). Run standalone for near-zero overhead.

What it catches

  • Heap memory leaks at program exit
  • Indirect leaks (leaked objects that own other leaks)

Does NOT catch

  • Stack leaks (there are none)
  • Buffer overflows (use ASan)
  • Data races (use TSan)

CMake integration

option(ENABLE_ASAN  "Enable AddressSanitizer + UBSan" OFF)
option(ENABLE_TSAN  "Enable ThreadSanitizer"            OFF)

if(ENABLE_ASAN)
  target_compile_options(myapp PRIVATE
    -fsanitize=address,undefined -fno-omit-frame-pointer -g)
  target_link_options(myapp PRIVATE
    -fsanitize=address,undefined)
endif()

if(ENABLE_TSAN)
  target_compile_options(myapp PRIVATE -fsanitize=thread -g)
  target_link_options(myapp PRIVATE -fsanitize=thread)
endif()

Build with: cmake -DENABLE_ASAN=ON ..

Deep-dive guides