[2026] C++ Sanitizers: ASan, TSan, UBSan, and MSan Explained

[2026] C++ Sanitizers: ASan, TSan, UBSan, and MSan Explained

이 글의 핵심

Clang/GCC sanitizers for C++: AddressSanitizer, ThreadSanitizer, UBSan, and CI examples. Catch buffer overflows, UAF, races, and UB with compile flags and examples.

Introduction

Sanitizers are compiler-assisted tools that detect memory errors, data races, and undefined behavior at runtime. They are faster than Valgrind for many workflows and provide accurate stack traces so you can fix bugs quickly.

1. Sanitizer overview

Main sanitizers

아래 코드는 bash를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# AddressSanitizer (ASan) — memory errors
g++ -fsanitize=address -g program.cpp -o program
# ThreadSanitizer (TSan) — data races
g++ -fsanitize=thread -g program.cpp -o program
# UndefinedBehaviorSanitizer (UBSan) — undefined behavior
g++ -fsanitize=undefined -g program.cpp -o program
# MemorySanitizer (MSan) — uninitialized memory
g++ -fsanitize=memory -g program.cpp -o program
# LeakSanitizer (LSan) — leaks (often bundled with ASan)
g++ -fsanitize=leak -g program.cpp -o program

Comparison

SanitizerDetectsOverheadPairing
ASanMemory errors~2×ASan + UBSan
TSanData races~5–15×Alone
UBSanUndefined behavior~1.2×ASan + UBSan
MSanUninitialized reads~3×Alone
LSanLeaksLowOften with ASan

2. AddressSanitizer (ASan)

Buffer overflow

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// bug1.cpp
#include <iostream>
int main() {
    int arr[10];
    
    // Bug: out of bounds
    for (int i = 0; i <= 10; ++i) {
        arr[i] = i;
    }
    
    return 0;
}

Build and run: 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

$ g++ -fsanitize=address -g bug1.cpp -o bug1
$ ./bug1
=================================================================
==12345==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc...
WRITE of size 4 at 0x7ffc....thread T0
    #0 0x....in main bug1.cpp:8
    #1 0x....in __libc_start_main
    
Address 0x7ffc....is located in stack of thread T0 at offset 40 in frame
    #0 0x....in main bug1.cpp:4

Use-after-free

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// bug2.cpp
#include <iostream>
int main() {
    int* ptr = new int(42);
    delete ptr;
    
    // Bug: use after free
    std::cout << *ptr << std::endl;
    
    return 0;
}

ASan output: 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

$ g++ -fsanitize=address -g bug2.cpp -o bug2
$ ./bug2
=================================================================
==12346==ERROR: AddressSanitizer: heap-use-after-free on address 0x602...
READ of size 4 at 0x602....thread T0
    #0 0x....in main bug2.cpp:7
    
0x602....is located 0 bytes inside of 4-byte region [0x602..., 0x602...)
freed by thread T0 here:
    #0 0x....in operator delete(void*)
    #1 0x....in main bug2.cpp:5

Memory leak

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

// bug3.cpp
#include <iostream>
int main() {
    // Bug: leak
    int* leak = new int(42);
    
    return 0;  // missing delete
}

ASan output: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

$ g++ -fsanitize=address -g bug3.cpp -o bug3
$ ./bug3
=================================================================
==12347==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4 byte(s) in 1 object(s) allocated from:
    #0 0x....in operator new(unsigned long)
    #1 0x....in main bug3.cpp:5

3. ThreadSanitizer (TSan)

Data race

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// race.cpp
#include <thread>
#include <iostream>
int counter = 0;
void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;  // data race!
    }
}
int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    
    t1.join();
    t2.join();
    
    std::cout << "counter: " << counter << std::endl;
    
    return 0;
}

TSan output: 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

$ g++ -fsanitize=thread -g race.cpp -o race
$ ./race
==================
WARNING: ThreadSanitizer: data race (pid=12348)
  Write of size 4 at 0x....by thread T1:
    #0 increment() race.cpp:7
  
  Previous write of size 4 at 0x....by thread T2:
    #0 increment() race.cpp:7

Fix: 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> counter{0};
void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;  // atomic
    }
}
int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    
    t1.join();
    t2.join();
    
    std::cout << "counter: " << counter << std::endl;  // 200000
    
    return 0;
}

4. UndefinedBehaviorSanitizer (UBSan)

Undefined behavior

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ub.cpp
#include <iostream>
int main() {
    // Bug 1: division by zero
    int x = 0;
    int y = 5 / x;
    
    // Bug 2: signed overflow
    int max = 2147483647;
    int overflow = max + 1;
    
    // Bug 3: null pointer dereference
    int* ptr = nullptr;
    int value = *ptr;
    
    // Bug 4: out-of-bounds array index
    int arr[10];
    int index = 15;
    int val = arr[index];
    
    return 0;
}

UBSan output: 아래 코드는 bash를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

$ g++ -fsanitize=undefined -g ub.cpp -o ub
$ ./ub
ub.cpp:6:15: runtime error: division by zero
ub.cpp:10:23: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
ub.cpp:14:17: runtime error: load of null pointer of type 'int'
ub.cpp:19:17: runtime error: index 15 out of bounds for type 'int [10]'

5. CI/CD integration

CMake

다음은 cmake를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyProject)
set(CMAKE_CXX_STANDARD 17)
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
option(ENABLE_UBSAN "Enable UBSanitizer" OFF)
if(ENABLE_ASAN)
    add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
    add_link_options(-fsanitize=address)
endif()
if(ENABLE_TSAN)
    add_compile_options(-fsanitize=thread)
    add_link_options(-fsanitize=thread)
endif()
if(ENABLE_UBSAN)
    add_compile_options(-fsanitize=undefined)
    add_link_options(-fsanitize=undefined)
endif()
add_executable(myapp main.cpp)

Build: 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# ASan
cmake -DENABLE_ASAN=ON ..
make
# TSan
cmake -DENABLE_TSAN=ON ..
make
# UBSan
cmake -DENABLE_UBSAN=ON ..
make

GitHub Actions

다음은 yaml를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# .github/workflows/sanitizers.yml
name: Sanitizers
on: [push, pull_request]
jobs:
  asan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Build with ASan
        run: |
          g++ -fsanitize=address -g src/*.cpp -o test_asan
      
      - name: Run Tests
        run: ./test_asan
  
  tsan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Build with TSan
        run: |
          g++ -fsanitize=thread -g src/*.cpp -o test_tsan
      
      - name: Run Tests
        run: ./test_tsan
  
  ubsan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Build with UBSan
        run: |
          g++ -fsanitize=undefined -g src/*.cpp -o test_ubsan
      
      - name: Run Tests
        run: ./test_ubsan

6. Common issues

Issue 1: Performance overhead

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Typical overhead:
// - ASan: ~2×
// - TSan: ~5–15×
// - UBSan: ~1.2×
// ✅ Dev/test only
// ✅ Strip from release builds

Issue 2: Compatibility

아래 코드는 bash를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# ❌ ASan + TSan together (not supported this way)
g++ -fsanitize=address,thread program.cpp  # error
# ✅ Separate builds
g++ -fsanitize=address program.cpp  # ASan
g++ -fsanitize=thread program.cpp   # TSan
# ✅ ASan + UBSan is OK
g++ -fsanitize=address,undefined program.cpp

Issue 3: False positives / intentional patterns

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Suppress with attribute
__attribute__((no_sanitize("address")))
void intentionalOverflow() {
    // ...
}
// Or suppression file asan.supp
ASAN_OPTIONS=suppressions=asan.supp ./program

Issue 4: Debug symbols

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# ❌ No -g (weaker stacks)
g++ -fsanitize=address program.cpp
# ✅ Add -g
g++ -fsanitize=address -g program.cpp
# ✅ Lower optimization for clearer traces
g++ -fsanitize=address -g -O1 program.cpp

7. Bug-hunting example

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// buggy_program.cpp
#include <iostream>
#include <vector>
#include <thread>
class DataProcessor {
    std::vector<int> data;
    int counter = 0;
    
public:
    void leakyFunction() {
        int* leak = new int(42);
        // missing delete
    }
    
    void bufferOverflow() {
        int arr[10];
        for (int i = 0; i <= 10; ++i) {
            arr[i] = i;
        }
    }
    
    void useAfterFree() {
        int* ptr = new int(42);
        delete ptr;
        std::cout << *ptr << std::endl;
    }
    
    void dataRace() {
        auto increment = [this]() {
            for (int i = 0; i < 100000; ++i) {
                ++counter;  // no synchronization
            }
        };
        
        std::thread t1(increment);
        std::thread t2(increment);
        
        t1.join();
        t2.join();
    }
    
    void integerOverflow() {
        int max = 2147483647;
        int overflow = max + 1;
        std::cout << overflow << std::endl;
    }
};
int main() {
    DataProcessor processor;
    return 0;
}

Test script: 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#!/bin/bash
echo "=== AddressSanitizer ==="
g++ -fsanitize=address -g buggy_program.cpp -o test_asan
./test_asan
echo "=== ThreadSanitizer ==="
g++ -fsanitize=thread -g buggy_program.cpp -o test_tsan
./test_tsan
echo "=== UBSanitizer ==="
g++ -fsanitize=undefined -g buggy_program.cpp -o test_ubsan
./test_ubsan

8. Sanitizer options

Environment variables

아래 코드는 bash를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# ASan
export ASAN_OPTIONS=detect_leaks=1:halt_on_error=0
# TSan
export TSAN_OPTIONS=history_size=7:halt_on_error=0
# UBSan
export UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=0
./program

Useful ASan options

아래 코드는 bash를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

ASAN_OPTIONS=detect_leaks=1
ASAN_OPTIONS=detect_stack_use_after_return=1
ASAN_OPTIONS=detect_container_overflow=1
ASAN_OPTIONS=log_path=asan.log
ASAN_OPTIONS=detect_leaks=1:log_path=asan.log:halt_on_error=0 ./program

Summary

Key takeaways

  1. ASan: Memory errors (buffers, UAF, leaks)
  2. TSan: Data races
  3. UBSan: Undefined behavior (overflow, divide-by-zero)
  4. MSan: Uninitialized memory
  5. LSan: Leaks (often with ASan)
  6. Cost: Dev/test only (~2–15×)

Choosing a sanitizer

Bug typeSanitizerWhen
Buffer overflowASanAlways in dev
LeaksASan / LSanAlways
Use-after-freeASanAlways
Data raceTSanMultithreaded code
Integer UBUBSanArithmetic-heavy code
Uninit readsMSanComplex initialization

Practical tips

  • Local dev: ASan + UBSan
  • Threads: separate TSan build
  • CI: automate sanitizer builds
  • Release: no sanitizers

Next steps


... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3