definitely lost 的根源是分配后彻底丢失指针,堆栈末尾即分配点;必须启用 --track-origins=yes 追踪指针来源,否则无法定位丢失路径;still reachable 常掩盖真实泄漏,多线程需 join() 避免假阳性。

直接看definitely lost堆栈末尾的分配点
Valgrind 报 definitely lost 时,关键不是“谁没 free”,而是“谁分配了但彻底丢了指针”。堆栈里最后一行(最靠近你的代码那一行)就是问题源头——通常是 malloc、new 或容器内部分配,但后续没保存地址、提前覆盖、或作用域结束自动销毁。
常见场景包括:
- 函数内局部指针接收
malloc返回值,但没返回/没存到外部可访问位置 -
std::vector::data()或std::string::c_str()取出裸指针后,原容器被 move 或析构,裸指针变悬垂 - 异常路径绕过
free/delete,比如if (err) return;前没清理 - 结构体成员指针在构造时分配,但析构函数没写或被跳过(如基类析构非 virtual)
--track-origins=yes 必须开,否则找不到“指针怎么丢的”
只靠 definitely lost 的分配位置,你只能知道“内存哪来的”,不知道“指针哪去了”。--track-origins=yes 会额外记录每个未初始化/丢失指针的赋值链,比如:
by 0x123456: foo() (a.cpp:42) | | | -- origin: 0x7890AB: bar() (b.cpp:18) | | | -- origin: 0xDEF012: main() (main.cpp:7)
这说明 a.cpp:42 的指针来自 b.cpp:18,而那里又来自 main.cpp:7 的某次赋值。没有这个参数,你就只剩一个 malloc 地址,毫无上下文。
注意:--track-origins=yes 会让 Valgrind 慢 2–3 倍,但它解决的是“为什么找不到指针”这个根本问题,不是可选项。
别信 still reachable 是安全的,它常掩盖真实泄漏
很多人看到 still reachable 就忽略,觉得“反正还能访问”。但实际中,它往往是泄漏的前兆:
- 全局
std::map或缓存容器持续 insert 不 erase,内存越积越多,Valgrind 认为“指针还活着”,但业务上已无用 - 单例对象持有资源,在 main 结束前没显式 cleanup,导致所有子分配都被记为
still reachable - 线程局部存储(TLS)里的指针,主线程退出时子线程还在跑,Valgrind 收工太早,把本该由子线程释放的内存全算成
still reachable
查 definitely lost 时,顺手翻下 still reachable 的调用栈——如果大量集中在某个初始化函数(比如 init_cache()),那很可能就是缓存泄漏。
多线程下 definitely lost 很可能是 Valgrind 没等线程结束
主线程 return 后,子线程若还在 malloc / new,Valgrind 默认不等它们结束就汇总报告,那些还没被 free 的内存直接变成 definitely lost ——不是你漏了,是 Valgrind 没看到结局。
必须做到三件事:
- 主线程末尾对每个
std::thread显式调用join()(不能只detach()) - 运行命令加
--tool=memcheck --leak-check=full --track-origins=yes --read-var-info=yes - 编译用
-g -O0,否则join()调用栈可能被优化掉,Valgrind 看不到线程真正结束了
漏掉任何一项,definitely lost 都可能只是假阳性,浪费半天时间追一个不存在的 bug。











