valgrind memcheck在调用free/delete时实时校验指针状态,若指针非malloc/new分配或已被释放,立即报invalid free();重复释放、释放栈/未初始化地址均触发该错误,无需--leak-check参数,但需-g编译以精确定位源码行。

非法释放(double-free / free未分配内存)怎么触发报错
Valgrind Memcheck 会在程序调用 free、delete 或 delete[] 时实时校验指针状态,只要该指针不满足“由 malloc/new 分配且尚未被释放”的条件,就会立即报错。它不依赖程序退出后的扫描,而是运行中拦截每一次释放操作。
- 重复释放同一指针:
free(p); free(p);→ 触发Invalid free() / delete / delete[] - 释放栈/全局变量地址:
int x; free(&x);→ 报Address not stack'd, malloc'd or (recently) free'd - 释放未初始化指针:
int *p; free(p);→ 同样报地址非法(除非恰好是 NULL,此时free(NULL)是安全的) - 释放后再次释放或读写:
free(p); printf("%d", *p);→ 先报Invalid free(),后续可能再报Invalid read
关键参数要不要加?--leak-check=full 对非法释放有用吗
没用。--leak-check=* 系列参数只影响退出时的泄漏汇总,和非法释放检测完全无关。Memcheck 检测释放错误是默认开启、不可关闭的行为。
- 必须加的是
-g:否则报错只能显示汇编地址,看不到foo.c:42这类定位 -
--track-origins=yes不影响非法释放检测,但对“为什么这个指针是野的”有帮助(比如它来自未初始化的局部变量) -
--show-leak-kinds=all可以关掉,它只管泄漏分类,不参与释放合法性判断
典型报错信息怎么看
遇到非法释放,Valgrind 通常输出三段式结构,重点看中间的堆栈和第一行错误类型:
==12345== Invalid free() / delete / delete[] ==12345== at 0x4C2EDEB: free (vg_replace_malloc.c:530) ==12345== by 0x1092AB: cleanup (utils.c:77) ==12345== by 0x1093CD: main (main.c:33) ==12345== Address 0x4a2c040 is 0 bytes inside a block of size 16 alloc'd ==12345== at 0x4C2DB8F: malloc (vg_replace_malloc.c:299) ==12345== by 0x109288: init_buffer (utils.c:51)
- 第一行
Invalid free() / delete / delete[]是核心判定,说明释放动作本身非法 - “Address ... is 0 bytes inside a block ... alloc'd” 表示这个地址确实曾被分配过 —— 那基本就是 double-free
- 如果显示
Address 0x... is not stack'd, malloc'd or (recently) free'd,说明根本不是堆内存,比如是栈地址或随机值
容易被忽略的坑:优化级别和内联函数
高优化(如 -O2)可能导致函数内联,使 Valgrind 报错堆栈里看不到原始调用点,甚至把两次 free 合并成一次检查逻辑,掩盖 double-free。
- 务必用
-g -O0编译被测程序,否则utils.c:77可能变成???:??? - 如果用了
__attribute__((always_inline))或模板深度展开,Valgrind 可能只显示内联后的位置,需结合源码上下文反推 - 某些系统库(如 glibc 的 malloc 实现)在 debug 模式下自带检查,会提前 abort,导致 Valgrind 来不及介入 —— 此时反而要关掉
MALLOC_CHECK_环境变量











