AddressSanitizer 能在 double-free 或 use-after-free 发生前一刻精准定位非法内存访问,编译需加 -fsanitize=address -g,且不可与 valgrind 共用;混用 malloc/free 与 new/delete 是高频诱因,需严格区分。

用 AddressSanitizer 快速定位 double-free 或 use-after-free
“corrupted double-linked list” 是 glibc malloc 检测到堆元数据损坏后的 abort 提示,本质不是链表本身出错,而是 free() 时发现 chunk 的前后指针(fd/bk)被非法改写。AddressSanitizer 能在错误发生**前一刻**捕获非法内存访问,比 core dump 后看堆栈更直接。
编译时加 -fsanitize=address -g,运行即报错位置(比如某次 delete p 后又 delete p,或 p->next = ... 写越界):
==12345==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000030 at pc 0x000000401234 bp 0x7fffeef01230 sp 0x7fffeef01228
READ of size 8 at 0x602000000030 thread T0
#0 0x401234 in main /test.cpp:12
0x602000000030 is located 0 bytes inside of 16-byte region [0x602000000030,0x602000000040)
freed by thread T0 here:
#0 0x7f... in operator delete(void*) (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10b9a7)- 必须加
-g,否则行号丢失 - ASan 会禁用部分优化,
-O1可接受,但-O2可能掩盖某些越界 - 不兼容
valgrind,二者不能同时启用
检查 malloc/free 和 new/delete 是否混用
混用是触发该错误的高频原因:用 malloc() 分配的内存调 delete,或用 new 分配的调 free(),会导致 malloc 元数据结构错位,后续任意 free() 都可能报 corruption。
- 全局搜索
malloc、calloc、realloc、free,确认是否和new/delete出现在同一对象生命周期中 - 尤其注意 C 接口封装类:比如构造函数里
buf = (char*)malloc(n),析构却写delete[] buf - STL 容器(如
std::vector)内部用new,绝不能对其data()返回指针调free
排查野指针修改 next/prev 指针的常见模式
如果你自己实现了双向链表(比如带 prev 和 next 成员),真正破坏 fd/bk 的往往不是链表操作本身,而是其他代码把指针当普通内存写了。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 检查所有对节点指针的强制类型转换:比如
(int*)p然后写*(int*)p = 0,会覆盖prev字段 - 确认
sizeof(Node)是否被 pack 或对齐干扰——若结构体因#pragma pack(1)导致next偏移异常,后续通过指针算地址会越界 - 多线程环境下未加锁修改同一节点的
next/prev,可能造成中间状态被另一个线程读取并误用 - 用
memset(p, 0, sizeof(*p))初始化节点前,确保p已分配且未被free过
用 malloc 调试环境变量缩小范围
如果无法快速复现或 ASan 不适用(比如嵌入式交叉编译),可临时启用 glibc 自带的 malloc 检查:
运行前设置:MALLOC_CHECK_=3(最严格)或 MALLOC_CHECK_=2(abort on error):
env MALLOC_CHECK_=3 ./myapp
它会在每次 malloc/free 时校验 chunk 头部,出错时打印类似:
*** glibc detected *** ./myapp: free(): invalid next size (normal): 0x0000000001a2b010 ***
-
MALLOC_CHECK_=1只打印警告不 abort,容易错过 - 该检查本身有开销,且可能掩盖真实问题(比如提前 abort 掩盖了更早的越界)
- 它无法告诉你哪行代码写的错,只告诉你“free 时发现坏了”,仍需结合 core 和
bt回溯
真正难的是那些只在特定内存布局下才触发的写越界——比如某个 char buf[10] 后紧挨着 Node* next,越界写 buf[12] 刚好踩到 next 字段。这种 case 必须靠 ASan 或手动打日志+内存快照比对。

















