Valgrind 默认只在进程退出时检查内存泄漏,长连接服务中需手动调用 VALGRIND_DO_LEAK_CHECK 在连接处理结束前触发即时检测,并配合 --show-leak-kinds=definite,indirect 过滤真泄漏。

Valgrind 能查,但必须让连接真正“退出”后才开始汇总泄漏——否则它只看到中间态的内存占用,不是真正的泄漏。
为什么连接退出后不自动触发 leak-check?
Valgrind 的 --leak-check 默认只在程序主进程 main() 返回或调用 exit() 时扫描堆内存。如果你的程序是长连接服务(比如用 epoll + 循环 accept() 的 server),单个连接关闭通常只是 close(fd) 和释放局部变量,main() 还在跑,Valgrind 根本不启动泄漏分析。
- 常见误判:看到
in use at exit: X bytes就以为是泄漏,其实那是主线程全局缓存、日志缓冲区、连接池等“合法存活”的内存 - 真实泄漏藏在连接处理函数里:比如每次
handle_client()中malloc()了 buffer 却没free(),但函数返回后指针丢了 - 关键点:Valgrind 不关心“连接生命周期”,只认“进程生命周期”。你得主动让它在连接结束的那一刻快照堆状态
手动触发泄漏检查:用 VALGRIND_DO_LEAK_CHECK
在连接处理逻辑的末尾(比如 handle_client() 函数 return 前),插入 Valgrind 提供的宏,强制做一次独立 leak-check:
#include <valgrind/valgrind.h>
void handle_client(int fd) {
char *buf = malloc(4096);
read(fd, buf, 4096);
// ... 处理逻辑
free(buf); // 正确释放
// 关键:连接即将关闭,立即检查本次连接是否引入新泄漏
VALGRIND_DO_LEAK_CHECK;
}
- 这个宏会触发一次完整的
--leak-check=full级别扫描,输出带栈回溯的泄漏报告 - 必须编译时加
-g,否则只显示地址,看不到handle_client这类函数名 - 不要在多线程里频繁调用——它有开销,且可能干扰线程调度;只在 debug build 或单连接复现时用
避免假阳性:区分 definitely lost 和 reachable
Valgrind 报告里这四类泄漏中,只有 definitely lost 是真问题:
-
definitely lost:指针已出作用域,且无其他引用 → 你的连接处理函数里漏free()了 -
indirectly lost:被definitely lost块引用的内存 → 连带泄漏,根因还在前者 -
possibly lost:可能只是指针算术偏移导致 Valgrind 没追踪到,需结合代码确认 -
reachable:主线程还持有指针(比如连接池数组、全局 cache)→ 不是泄漏,是设计如此
所以运行时加 --show-leak-kinds=definite,indirect,过滤掉干扰项。
更实用的替代方案:用 --log-file + 连接 ID 打标
如果服务无法改源码(比如用第三方库),或者要批量压测多个连接,推荐这个方法:
- 启动时加
--log-file=valgrind-%p.log,%p 替换为进程 PID,避免多实例日志混在一起 - 在连接建立时,用
fprintf(stderr, "CONN_START %d\n", conn_id);打印标记 - 连接关闭前,再打
fprintf(stderr, "CONN_END %d\n", conn_id); - 跑完后用
grep -A20 "CONN_END 123" valgrind-*.log快速定位该连接结束附近的泄漏段
这个技巧不依赖代码修改,靠日志时间戳和上下文对齐,适合线上复现后离线分析。
最易被忽略的一点:Valgrind 的泄漏判定基于“程序退出时的堆快照”,而连接退出不是退出事件。不主动干预,它就永远看不见单次连接里的泄漏——哪怕你加了 --leak-check=full 也白搭。


















