启用AddressSanitizer(-fsanitize=address -g)可快速定位越界写操作,运行时精准报告heap-buffer-overflow及源码行号,适用于堆/栈数组,但仅限调试使用。

用 AddressSanitizer 快速定位越界写操作
越界写入导致其他变量被意外修改,最直接有效的排查方式是启用编译器的内存错误检测工具。Clang 和 GCC 都支持 AddressSanitizer(ASan),它能在运行时捕获对数组边界外内存的非法访问,并精确报告出错位置。
编译时加 -fsanitize=address -g 即可,例如:
g++ -fsanitize=address -g main.cpp -o main
运行后一旦发生越界写,会立刻打印类似这样的错误信息:
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000001c at pc 0x000000401234 bp 0x7ffeeda12340 sp 0x7ffeeda12338
WRITE of size 4 at 0x60200000001c thread T0
#0 0x401234 in main /path/main.cpp:12:15关键看 heap-buffer-overflow 和行号(如 main.cpp:12),那里就是指针越界写入的源头。
立即学习“C++免费学习笔记(深入)”;
- 必须加
-g,否则报错里看不到源码行号 - ASan 会显著拖慢运行速度、增加内存占用,仅用于调试,不要在发布版启用
- 静态数组、栈上数组越界也能捕获,但部分栈溢出场景(如超大局部数组)可能被优化干扰,优先复现到堆上验证
检查指针算术是否超出 data() + size() 范围
C++ 中用原始指针遍历容器或数组时,常见错误是把 end 当成“最后一个元素地址”,而实际应为“末尾之后一个位置”。比如对 std::vector<int> 或 std::array<int, N> 取 data() 后做指针偏移,很容易多走一步。
正确做法是确保所有指针访问满足:ptr >= arr.data() 且 ptr 。别依赖 <code>arr.size() - 1 做上界判断——那容易漏掉空容器或整数溢出情况。
- 用
std::span<T>替代裸指针能强制边界检查(启用span_bounds检查时),但需 C++20 支持 - 若用
for (int* p = arr.data(); p != arr.data() + arr.size(); ++p),确认arr.size()是size_t,避免有符号/无符号混用导致循环不退出 - 对 C 风格数组
int a[10],sizeof(a)/sizeof(*a)才是安全长度,别用未初始化的变量或宏定义值代替
观察变量地址变化来反向追踪污染源
当发现某个变量值异常突变,又没找到明显赋值点时,可以打印它的地址和周围变量的地址,看是否紧邻被修改的数组内存块。
例如:
int arr[5] = {0};
int flag = 42;
std::cout << "arr: " << (void*)arr << "\n";
std::cout << "flag: " << (void*)&flag << "\n";如果输出显示 flag 地址就在 arr + 5 附近,那大概率是 arr[5] = ... 或 *(arr + 5) = ... 导致的覆盖。
- 开启优化(如
-O2)会打乱变量布局,调试时务必关闭优化(-O0) - 不同编译器、不同调用栈深度下,栈上变量顺序不固定,单次观察不够可靠,要结合 ASan 报告交叉验证
- 全局变量和静态变量通常单独存放在数据段,一般不会被栈上数组越界覆盖;被改的往往是同作用域的相邻局部变量
用 valgrind --tool=memcheck 作为 ASan 的补充验证
某些嵌入式交叉编译环境或旧版本 GCC 不支持 ASan,此时 valgrind 是替代选择。它通过动态二进制插桩检测非法内存访问,兼容性更好,但性能开销更大、对信号处理或内联汇编敏感。
运行命令:
valgrind --tool=memcheck --track-origins=yes ./main
--track-origins=yes 能指出被污染值最初从哪来,对追查“谁改了 flag”这类问题很有用。
- valgrind 无法检测未初始化内存读取以外的栈溢出(如函数内数组越界),对栈上越界覆盖的提示不如 ASan 直接
- 程序需用 glibc 编译,musl 或裸机环境不适用
- 输出中关注
Invalid write of size X和Address 0x... is 4 bytes after a block of size 20 alloc'd这类线索
真正难的不是发现越界,而是当代码路径复杂、条件分支多、或指针经过多次传递后,你得确认那个 ptr + i 的 i 是不是在某次循环中悄悄超出了 len。这时候光看单个函数不够,得顺着调用栈往上翻——尤其注意参数传入时有没有做长度校验,或者中间是否有人把 size_t 强转成了 int 导致截断。


















