TopC++ 入門 › 実践 › Sanitizer

AddressSanitizer / UBSan 入門

C++ のクラッシュの大半は「解放済みメモリ参照」「配列外書き込み」「nullptr 参照」「UB(未定義動作)」。Sanitizer はフラグ 1 本でこれらを即検出し、発生した行・原因の割当行を吐きます。開発中は必ず使いましょう。

1. 使い方 — -fsanitize=... を付けるだけ

$ clang++ -std=c++17 -g -O1 -fsanitize=address,undefined main.cpp -o app $ ./app ==12345==ERROR: AddressSanitizer: heap-use-after-free on address ... READ of size 4 at 0x60200000... thread T0 #0 0x... in process(int*) main.cpp:10 freed by thread T0: #0 0x... in operator delete(void*) #1 0x... in main main.cpp:23 previously allocated by: #1 0x... in main main.cpp:20

発生箇所解放箇所割当箇所の 3 つが一気に表示されます。bug 特定が劇的に早くなります。

2. Sanitizer の種類

名前フラグ検出対象オーバーヘッド
ASan-fsanitize=addressuse-after-free, heap OOB, stack OOB, double-free2–3x
UBSan-fsanitize=undefined符号付き overflow, 0 除算, null 参照, 整数→enum…~1.2x
TSan-fsanitize=threadデータ競合、デッドロック5–15x
MSan-fsanitize=memory未初期化メモリ読み取り2–4x(再ビルド必要)
LSan-fsanitize=leakメモリリーク
ASan と TSan は同時使用不可。普段は address,undefined の組み合わせ、並行コードのデバッグ時に TSan に切り替え、が定番。

3. 実例 1 — use-after-free

bug.cpp
int* make() { int* p = new int(42); delete p; return p; // 解放後のポインタを返す } int main() { std::cout << *make(); // use-after-free }
ASan 出力
heap-use-after-free at 0x... READ of size 4 at 0x... #0 main.cpp:8 (main) freed by: #0 main.cpp:3 (make) allocated by: #0 main.cpp:2 (make)

4. 実例 2 — 配列外アクセス

bug.cpp
std::vector<int> v(10); for (int i = 0; i <= 10; ++i) // ← ≤ は境界越え v[i] = i;
ASan 出力
heap-buffer-overflow WRITE of size 4 at ... main.cpp:3

5. 実例 3 — 整数オーバーフロー(UBSan)

bug.cpp
int x = INT_MAX; int y = x + 1; // signed overflow UB
UBSan 出力
runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int' main.cpp:2

6. CI に組み込む

Pull Request ごとに Sanitizer 付きビルドを走らせるとUB バグが永続的に減る。GitHub Actions での例:

name: ASan CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: | cmake -B build -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -g" cmake --build build ctest --test-dir build --output-on-failure

7. 注意点

  • 本番ビルドには使わない。ASan は 2–3 倍遅い。
  • リンク時も同じフラグが必要(-fsanitize=... を両段で)。
  • コンパイラ統一: g++ 版と clang++ 版の ASan ライブラリは互換性が無い。
  • fork / exec する子プロセス側にも反映されないことがある。環境変数 ASAN_OPTIONS で制御可能。
Valgrind との違い: Valgrind は再コンパイル不要だが 20–30 倍遅い。ASan は 2–3 倍だが再コンパイル要。現代は基本的に ASan を選ぶのが主流。