查“用了未初始化指针”必须加 --track-origins=yes,否则valgrind仅报“conditional jump or move depends on uninitialised value(s)”而无法定位声明或赋值行;启用后可精准追溯至源码第x行,代价是性能下降2–3倍。

查“用了未初始化指针”必须加 --track-origins=yes
Valgrind 默认只告诉你“Conditional jump or move depends on uninitialised value(s)”,但不会指出这个未初始化值是从哪来的。比如局部指针 p 声明后没赋值,后续用 if (p) 判断,Memcheck 会报错,但调用栈只到 printf 或 strcmp 这类库函数里——根本看不到你代码里哪一行漏了初始化。
加 --track-origins=yes 后,它会一路追到 p 第一次被读取的位置,甚至能定位到声明行(前提是编译带 -g)。
- 不加这个参数:报错只显示“用了未初始化值”,但来源模糊
- 加了之后:输出里会出现类似
by 0x401234: main (test.cpp:12),直接指向你漏初始化的那一行 - 代价是运行慢 2–3 倍,但查指针问题时值得
查“释放后继续用指针(UAF)”看 Invalid read/write 的调用栈
这类问题 Valgrind 报错很明确:Invalid read of size 8 或 Invalid write of size 4,后面紧跟着两段关键信息:一是分配位置(malloc / new 行),二是释放位置(free / delete 行),最后才是出问题的使用位置。
重点不是第一行错误提示,而是它下面连续的三段 at 0x... 调用栈:
- 最上面一层:出问题的代码行(比如
p->x = 1) - 中间一层:
free(p)或delete p那一行 - 最下面一层:当初
p = new T或p = malloc(...)的位置
如果中间那层没看到 free/delete,说明可能根本没释放过——那问题可能是野指针(压根没 malloc 就用了),而不是 UAF。
查“指针越界访问”注意区分堆块上下溢
Valgrind 对越界分得很细:Invalid read of size N 后面会标注地址是否在堆块内、是否紧邻边界、是否明显超出。常见两类:
-
Address 0x... is 4 bytes after a block of size 16 alloc'd:上溢,比如arr[4]访问了int arr[4]分配的 16 字节之后 -
Address 0x... is 8 bytes before a block of size 32 alloc'd:下溢,比如把指针往回挪了 8 字节再解引用
它不会告诉你“数组下标写错了”,但会精准指出越了多少字节、原块多大、在哪分配的——结合源码里的 malloc 大小和指针运算逻辑,基本能反推出哪次偏移越界了。
查“指针类型不匹配释放”看 Mismatched free / delete 提示
比如用 new[] 分配的数组,却用 delete(没方括号)释放,Valgrind 会单独报一条:Mismatched free() / delete / delete [],并附上分配和释放两处的调用栈。
这种错误不一定立刻崩溃,但属于未定义行为。Valgrind 能捕获的关键前提是:你得用 --tool=memcheck(默认就是),且程序实际执行到了那条错误释放语句。
- 常见组合错误:
malloc+delete、new+delete[]、new[]+delete - 它不检查
new和delete是否在同一个线程,也不管 RAII 容器内部怎么管理——只盯住裸指针的分配/释放对
真正难定位的是间接持有:比如指针被拷贝进多个对象,某个析构函数误删了一次,另一个还在用。Valgrind 只能告诉你“这里删错了”,但不会自动画出所有权图——得靠你顺着调用栈人工倒查。











