AddressSanitizer可快速定位越界写入点,编译时加-fsanitize=address -g且禁用优化,第三方库也需ASan编译;std::vector::operator[]越界能被检测,而at()抛异常不触发ASan但可辅助调试。

用 AddressSanitizer 快速定位越界写入点
AddressSanitizer(ASan)是目前最有效的运行时检测手段,它能在越界发生瞬间报错并给出完整调用栈。GCC 和 Clang 都原生支持,无需改代码,只要加编译选项即可生效。
编译时加上 -fsanitize=address -g,例如:g++ -fsanitize=address -g -O0 main.cpp -o main
-
-O0强烈建议开启,优化可能隐藏栈帧或内联函数,导致 ASan 报错位置不准确 - 链接第三方静态库时,该库也需用 ASan 编译过,否则越界发生在库内可能只显示“unknown module”
- 报错信息里关键字段是
WRITE of size X at 0x... thread T0,后面紧跟的#0 ... in ... at xxx.cpp:NN就是越界赋值那行
std::vector 越界访问为什么没触发 ASan?
因为 std::vector::operator[] 不做边界检查——它和裸指针一样,越界就是纯内存覆盖。ASan 能捕获它,但前提是越界地址落在 ASan 监控的“影子内存”范围内(通常没问题)。真正容易漏掉的是 at() 抛异常的场景:它不会触发 ASan,但会中止程序,且堆栈更干净。
- 开发阶段可临时把
v[i]改成v.at(i),让越界立刻 crash 并打印std::out_of_range异常信息 - 注意
at()有运行时开销,上线前应换回[],但逻辑分支必须确保索引合法(比如i ) - 对
std::vector<bool></bool>要格外小心——它是特化实现,底层可能位压缩,越界行为更难预测,ASan 有时无法精准标记
手动管理 new[] / delete[] 时的典型越界模式
裸动态数组是最容易出问题的场景,常见错误不是“多写一个”,而是循环条件、长度计算、size/sizeof 混淆导致的系统性偏移。
立即学习“C++免费学习笔记(深入)”;
- 写循环时用
for (int i = 0; i (应为 <code>),这种错误 ASan 一定能抓到 - 误用
sizeof(arr)获取元素个数(对指针返回指针大小),导致后续循环跑飞;正确做法是单独存size_t len = n; - 释放后继续使用
arr[i] = ...(use-after-free),ASan 同样能报,错误信息含heap-use-after-free - 混用
new[]和delete(少 []),这属于未定义行为,不一定立即崩溃,但会破坏堆管理结构,后续任意new或delete都可能崩,ASan 无法覆盖这类元数据破坏
Valgrind 的 memcheck 在什么情况下比 ASan 更有用?
ASan 对栈、全局、堆内存全覆盖,但依赖编译器插桩;Valgrind 是二进制翻译,不需重新编译,适合排查已发布的黑盒程序,或与 ASan 冲突的环境(如某些内联汇编、信号处理密集型程序)。
- 运行命令:
valgrind --tool=memcheck --leak-check=full ./main - 关注输出中的
Invalid write of size X及其上下文,注意 Valgrind 报的行号可能滞后(因指令翻译延迟),要结合附近几行看 - Valgrind 对
std::vector内部缓冲区越界也能捕获,但对 STL 容器的封装层有时显示为operator new分配点,不如 ASan 直接定位到用户代码行 - 性能开销极大(5–10 倍),仅用于调试,别在压测或 CI 中启用
越界覆盖堆内存的本质,是写到了 malloc 元数据区域或相邻 chunk 的用户区,轻则后续分配失败,重则静默改写其他对象字段。ASan 和 Valgrind 都不能替代代码审查——尤其要盯住所有 new[n]、malloc(n * sizeof(T)) 和循环终止条件。


















