valgrind定位堆泄漏需带调试符号编译(gcc -g -o0),关注含文件名和行号的分配点;它仅跟踪malloc/new等,不监控mmap/brk;长时服务宜用gdb动态断点或/proc/pid/smaps分析pss。

用 valgrind 抓住堆内存泄漏的准确位置
多数人跑 valgrind --leak-check=full ./a.out 后只扫一眼“definitely lost”就停了,但真正有用的是它输出中带文件名和行号的那一行——前提是你的程序得带调试符号编译。
- 必须用
gcc -g -O0编译,-O2会让内联和变量优化掩盖真实分配点 -
valgrind对mmap、brk级别分配不敏感,只管malloc/calloc/realloc系列,C++ 的new也算在内 - 如果程序一启动就崩溃,加
--tool=memcheck --track-origins=yes查未初始化内存是否间接导致后续误释放 - 注意
suppressions文件干扰:默认 suppressions 可能过滤掉 glibc 内部的假阳性,但自定义 suppressions 写错会漏报真泄漏
gdb 里动态观察 malloc 调用栈
不是所有泄漏都适合等程序退出再查;长时运行服务(比如后台 daemon)需要在线盯住谁在不断 malloc 却不 free。
- 先
gdb -p $(pidof your_service),然后catch syscall mmap或break malloc,再command 1; bt; cont; end让每次分配都打个断点栈 - 更轻量的做法是
watch *(int*)0xdeadbeef—— 先用cat /proc/PID/maps找到堆地址范围,再对某块堆内存设写入观察点,触发时看谁在改它 - 注意
glibc的malloc实现有 fastbin、tcache,小对象可能复用不走系统调用,这时候catch syscall brk就捕不到,得回退到break __libc_malloc
/proc/PID/smaps 看懂 RSS 和 PSS 差异
运维常盯着 top 的 %MEM 或 ps aux 的 VSZ 判断泄漏,但这些数字根本不能定位问题——VSZ 包含没实际映射的虚拟地址,RSS 又包含共享库和 mmap 共享内存。
- 真正反映进程独占物理内存的是
PSS(Proportional Set Size),在/proc/PID/smaps每个内存段后都有,总和才接近真实增长量 - 重点看
AnonHugePages和MMUPageSize字段:如果某段Size很大但RSS接近 0,说明只是预留了虚拟地址,还没真正分配物理页 - 用
awk '/^Size:/ {s+=$2} /^PSS:/ {p+=$2} END {print "Size:", s, "PSS:", p}' /proc/PID/smaps快速汇总,比单看top可靠得多
C++ 中 std::shared_ptr 循环引用的真实表现
不是所有 C++ 内存泄漏都报错或 crash,shared_ptr 循环引用会导致对象永远不析构,但 valgrind 也标为 “still reachable”,容易被当成正常缓存。
- 典型模式:
A持有shared_ptr<b></b>,B又持有shared_ptr<a></a>;用weak_ptr替换其中一端才能打破循环 -
valgrind输出里如果看到大量 “still reachable” 且堆栈指向std::shared_ptr<...>::_M_release</...>,基本就是这个原因 - 不要依赖
std::enable_shared_from_this自动管理,它本身不解决循环,反而可能让引用关系更隐蔽
堆内存泄漏最难的不是发现,是确认那个 malloc 调用到底该由谁 free——尤其是跨模块、跨线程、或者封装在第三方库回调里的分配。这时候看调用栈比看代码行号还重要。










