启用ASan需-fsanitize=address -fno-omit-frame-pointer,TSan需-fsanitize=thread -fno-omit-frame-pointer(不可与ASan/MSan共用),UBSan需-fsanitize=undefined(可叠加子项),且Clang更推荐。

编译时加什么 flag 才能用上 ASan/TSan/UBSan
不改代码,只靠编译器开关就能启用 sanitizer —— 但必须确保链接阶段也一致。Clang 和 GCC 都支持,但行为略有差异,GCC 11+ 对 TSan 支持仍不稳定,建议优先用 Clang。
- ASan(内存越界、UAF、栈缓冲区溢出):
-fsanitize=address -fno-omit-frame-pointer,-fno-omit-frame-pointer是必须的,否则堆栈追踪会断 - TSan(数据竞争):
-fsanitize=thread -fno-omit-frame-pointer,注意:不能和 ASan/MSan 同时用,会冲突 - UBSan(未定义行为:整数溢出、空指针解引用、类型别名违规等):
-fsanitize=undefined,可叠加子项如-fsanitize=signed-integer-overflow,null,alignment - 所有 sanitizer 默认开启运行时检查开销;若要关掉某类(比如跳过浮点异常),得显式禁用:
-fno-sanitize=float-divide-by-zero
运行时报 ERROR: AddressSanitizer: heap-use-after-free 怎么定位
ASan 报错自带完整调用栈,但前提是没被 strip 或优化干扰。Release 模式下 -O2 可能内联关键函数,导致栈帧丢失 —— 所以开发阶段务必用 -O1 或 -O0 配合 ASan。
- 报错里出现
freed by thread T0 here:和previously allocated by thread T0 here:是关键线索,对应 malloc 和 free 的位置 - 如果看到
Shadow bytes around the buggy address:下面全是fa或fd,说明是 heap use-after-free;cb通常表示 stack buffer overflow - 多线程下 UAF 可能跨线程触发,ASan 能捕获但不会告诉你哪条线程“真正”释放了内存,得结合
ThreadSanitizer交叉验证 - 避免误判:全局对象析构顺序问题有时也表现为 UAF,尤其在
atexit注册的清理函数中访问已销毁单例
TSan 报 Data race on variable 'g_counter' 却没锁,怎么修
TSan 不关心你“觉得”有没有竞争,只看内存地址是否被多个线程无同步地读写。哪怕变量是 int、看似原子,只要没加同步原语,就报。
- 最简修复是加
std::atomic<int>,但注意:不是所有操作都默认序,load()/store()默认是memory_order_seq_cst,够用;若手动指定弱序,TSan 仍可能报(它只认同步动作,不推理语义) - 用
std::mutex保护时,确保所有访问路径都走同一把锁 —— 常见坑是忘了某个分支或 lambda 捕获里偷偷读写 - TSan 会忽略
std::atomic_thread_fence,除非配合原子操作;单纯打个 fence 不算同步点 - 第三方库(如某些日志模块)内部有静态变量初始化竞争,TSan 会报但你改不了源码,可用
__tsan_ignore_reads_begin()临时屏蔽(慎用,仅限确认无害)
UBSan 报 signed integer overflow 但逻辑上不可能发生
UBSan 在编译期插桩检测运行时行为,不分析控制流。所谓“逻辑上不可能”,往往是常量传播或符号执行没覆盖到的边界分支,或者你低估了输入范围。
立即学习“C++免费学习笔记(深入)”;
- 典型场景:循环里
i * 1000累加,输入 i 来自文件或网络,UBSan 发现某次乘法溢出,而你测试时只用了小值 -
-fsanitize=undefined默认不捕获浮点异常,如需检查除零或 NaN,得加float-divide-by-zero或float-cast-overflow - 有些头文件(如
<algorithm>)里模板实例化可能引入隐式转换,UBSan 会报implicit-conversion,这时加-fno-sanitize=implicit-conversion更合理,而不是硬改算法 - Release 构建里关掉 UBSan 很常见,但要注意:某些 UB(如移位超界)在不同架构下表现不同,x86 可能静默截断,ARM 可能 trap —— 开发期不暴露,上线后才崩
sanitizer 不是银弹:它们依赖插桩,会拖慢 2–5 倍,且无法覆盖 fork 后子进程、dlopen 动态加载的代码、或 signal handler 里的逻辑。真要压测或线上灰度,得搭配静态分析(如 clang-tidy)和 fuzzing 才算兜住底。











