AddressSanitizer可直接在运行时捕获nullptr解引用,包括*ptr、ptr->field等形式,编译时添加-fsanitize=address -g即可启用,对std::unique_ptr/std::shared_ptr的解引用同样有效,但不检测未初始化指针;其开销约2倍时间、1.5倍内存,推荐-O0调试以保留准确调用点。

用 AddressSanitizer 快速捕获运行时空指针解引用
AddressSanitizer(ASan)是 GCC 和 Clang 内置的内存错误检测器,能直接在运行时报告 nullptr 解引用的位置,包括 ptr->field、*ptr、ptr[0] 等典型形式。它不依赖源码注释或手动断言,对未定义行为敏感,且开销可控(约 2× 时间、1.5× 内存)。
编译时加 -fsanitize=address -g 即可启用:
g++ -fsanitize=address -g -O0 crash.cpp -o crash ./crash
触发空指针解引用后会输出类似:
==12345==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x555555556123 bp 0x7ffd12345678 sp 0x7ffd12345650 T0)
#0 0x555555556123 in foo() /tmp/crash.cpp:5
-
-O0推荐用于调试,避免内联掩盖原始调用点 - ASan 对
std::unique_ptr和std::shared_ptr的operator->/operator*同样有效 - 不检测未初始化指针(如
int* p;后直接*p),只检测明确为nullptr或已释放地址的访问
静态检查:Clang-Tidy 的 cppcoreguidelines-pro-bounds-pointer-arithmetic 和 clang-analyzer-core.NullDereference
Clang-Tidy 可在编译前发现部分明显空解引用路径,尤其适合 CI 阶段拦截低级错误。但注意:它基于控制流分析,无法覆盖所有分支,也不能替代运行时检测。
立即学习“C++免费学习笔记(深入)”;
启用方式:
clang++ -std=c++17 crash.cpp -Xclang -analyzer-checker=core.NullDereference # 或用 clang-tidy clang-tidy -checks='clang-analyzer-core.NullDereference' crash.cpp
- 对
if (p == nullptr) return; *p;这类紧邻写法识别率高 - 对跨函数调用(如
foo(p); bar(*p);)需开启-Xclang -analyzer-config -Xclang widen-zealous-region-inits=true提升精度 - 误报常见于模板元编程或 SFINAE 场景,建议搭配
// NOLINT局部抑制
运行时防御:用 assert(ptr != nullptr) 或 [[likely]] + 显式检查
在关键路径(如库接口入口、性能不敏感的配置解析)插入显式判空,是最直接可控的手段。C++20 起可结合 [[likely]] 减少分支预测惩罚,但前提是空指针属于异常路径。
示例:
void process(const Data* ptr) {
assert(ptr != nullptr && "process: ptr must not be null");
// 或更明确:
if (ptr == nullptr) [[unlikely]] {
throw std::invalid_argument("ptr is null");
}
use(*ptr);
}
-
assert在NDEBUG下被移除,生产环境必须用显式if+ 错误处理 - 不要对
std::optional<t>::value()</t>或std::vector::at()等已有边界检查的接口重复判空 - 对返回裸指针的 C API(如
fopen、malloc)必须检查,不能依赖 ASan 替代逻辑正确性
为什么 std::optional<t></t> 和 std::unique_ptr 不能完全替代空指针检测
它们能提升接口语义清晰度,但不会自动防止解引用空值——std::optional<t>::value()</t> 在无值时仍抛 std::bad_optional_access,而 std::unique_ptr::operator-> 对空指针的行为仍是未定义的(尽管多数实现会崩溃)。
关键点:
-
std::optional<t> opt = nullptr;</t>是合法的,但opt.value()->field仍会崩 -
std::unique_ptr<t> p;</t>后调用p->field仍是 UB,ASan 依然能捕获 - 这些类型的价值在于“迫使调用方考虑空状态”,而非“自动兜底”
真正省心的是把裸指针换成引用(T&)或非空包装(如 not_null<T*> from GSL),但前提是能控制接口契约。


















