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=address | use-after-free, heap OOB, stack OOB, double-free | 2–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 を選ぶのが主流。