编译时启用UBSan需显式添加-fsanitize=undefined -g -O1,Clang更推荐;默认检测整数溢出、空指针解引用等,细粒度检查需手动追加如-fsanitize=signed-integer-overflow。

怎么编译时启用 UBSan
UBSan 不是运行时自动开启的工具,必须在编译阶段显式加入 -fsanitize=undefined,否则程序完全不会做未定义行为检查。GCC 和 Clang 都支持,但 Clang 的实现更完整、报错更清晰,推荐优先用 Clang。
常见错误是只加了 -fsanitize=address(ASan)却误以为也启用了 UBSan——两者完全独立,必须单独指定。
- Clang 编译示例:
clang++ -fsanitize=undefined -g -O1 main.cpp -o main - 启用整数溢出专项检查(默认已包含,但可显式加强):
-fsanitize=signed-integer-overflow,unsigned-integer-overflow - 避免优化干扰检测(尤其
-O2以上可能把溢出代码直接优化掉):建议用-O1或-O0配合 UBSan - 务必加
-g:否则报错时看不到源码行号,只剩汇编地址,基本无法定位
哪些未定义行为默认会被捕获
UBSan 默认开启的检查项其实有限,并非所有 C++ 标准里写的“未定义行为”都会报。它聚焦在**可静态插桩、运行时开销可控**的类别上。比如 int x = INT_MAX + 1; 会触发,但 delete p; delete p;(重复释放)就不会——那是 ASan 的职责。
- 默认启用的典型检查:
signed-integer-overflow、unsigned-integer-overflow(仅当使用-fwrapv以外模式)、shift(左移位数 >= 位宽或负数位移)、null-dereference、builtin-unreachable - 不默认启用但常被需要的:
alignment(内存对齐违规)、vla-bound(变长数组越界)、float-cast-overflow(浮点转整数溢出),需手动追加到-fsanitize=后面 -
std::vector::at()越界访问不会触发 UBSan —— 它是库级检查,依赖-D_GLIBCXX_DEBUG,和 UBSan 无关
运行时报错怎么看、怎么修
UBSan 报错格式直白,但关键信息容易被忽略。典型输出像:main.cpp:12:27: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'。重点不是“error”这个词,而是冒号后的位置和“cannot be represented in type”这类定性描述。
立即学习“C++免费学习笔记(深入)”;
- 看到
shift exponent错误,先查位移右操作数是否为负或 ≥ 左操作数位宽,不要只盯着左值 - 若报错出现在内联函数或模板实例化中,堆栈可能只显示最外层调用——此时要结合
-g和addr2line -e main 0x...反查真实位置 - 修复不等于加判断。例如
if (a > INT_MAX - b) { /* 处理溢出 */ }比if (a + b 更可靠(后者在无符号下成立,有符号下仍是未定义) - 某些场景该用
std::add_overflow(C++23)或__builtin_add_overflow(GCC/Clang 内建函数)替代裸加法
UBSan 和其他 sanitizer 能不能一起用
可以,但有明确限制:UBSan 必须和 ASan/MSan **分开运行**,不能同时链接。Clang 允许 -fsanitize=undefined,address 这种写法,但实际会静默禁用 UBSan 的部分检查(尤其是内存相关),导致漏报。
- 正确做法:分两次构建——一次专测未定义行为(
-fsanitize=undefined),一次专测内存错误(-fsanitize=address) - TSan(线程检查)可以和 UBSan 共存:
-fsanitize=thread,undefined是安全的组合 - 生产环境绝对不要带 UBSan 发布:它会让二进制体积增大 20%+,性能下降 2–5 倍,且所有检查都是调试用途,不是防御机制
- CI 中建议固定用
-O1 -g -fsanitize=undefined跑单元测试,比靠人眼 Review 更早暴露隐晦溢出
UBSan 的真正难点不在开关,而在理解它“不报错≠没毛病”——它只覆盖可检测子集,且高度依赖编译器插桩逻辑。一旦代码被过度优化或跨翻译单元内联,某些溢出就再也看不到了。


















