《valgrind —— 内存调试、内存泄漏检测以及性能分析》
内存泄露的几种原因:
动态内存分配未释放:使用 malloc
、calloc
、realloc
或 new
(C++)分配内存后,未调用对应的 free
或 delete
释放。
文件描述符或资源泄漏:打开文件、套接字、设备驱动等资源后未关闭,间接导致内核内存泄漏。
缓存或数据结构未清理:动态构建的链表、树等数据结构未释放所有节点或缓存(Cache)未设置淘汰策略,导致内存无限增长。
多线程/进程竞争:进程间通信(如共享内存)未正确管理生命周期。
第三方库或驱动泄漏:库的API使用不当(如未调用初始化/清理函数)。
内存碎片化:频繁分配/释放不同大小的内存,导致可用内存碎片化,无法分配大块内存。
1.valgrind
主要定位解决以下几种场景:内存泄露、非法内存访问、多线程竞争与死锁、内存碎片与分配器性能、文件描述符与资源泄露。
- 内存占用:Valgrind 通过动态二进制插桩(DBI)技术监控内存操作,运行时需加载目标程序和自身工具链(如 Memcheck),内存占用通常为原程序的 2-10 倍。例如,若原程序占用 10MB 内存,Valgrind 可能额外消耗 20MB-100MB。
- 适用场景:开发阶段调试复杂内存问题(如多线程竞争、条件跳转依赖未初始化值),但不适用于资源极度受限的嵌入式设备(如 RAM < 64MB 的 MCU)。
- 优化建议:
- 限制监控范围:通过
--suppressions
过滤无关模块,减少内存开销。 - 使用轻量级工具:如
valgrind --tool=none
仅加载基础框架,不启用具体工具。
- 限制监控范围:通过
内存泄漏:
demo:

#include <stdlib.h> #include <stdio.h> #include <signal.h> #include <unistd.h> static char* global_buf = NULL; // 全局变量,持有内存 void handler(int sig) { printf("Received signal %d, exiting...\n", sig); // 注意:未释放 global_buf! exit(0); } int main() { signal(SIGINT, handler); // 注册 Ctrl+C 处理器 char *p = NULL; int count = 0; global_buf = malloc(1024); // 分配内存 printf("Allocated 1024 bytes at %p\n", global_buf); while (1) { // 持续运行 sleep(5); p = malloc(1024); count++; printf("malloc leak count=%d \n", count); } return 0; // 实际不会执行到这里 }
编译需要添加-g选项
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./a.out
--leak-check=full
:详细报告泄漏位置。--show-leak-kinds=all
:显示所有类型的泄漏(definitely
/indirectly
/possibly
/still reachable
)。--track-origins=yes
:跟踪未初始化值的来源(对调试有帮助)。
运行一段时间后,通过ctrl+c退出程序,可以看到:

==327== Memcheck, a memory error detector ==327== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==327== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==327== Command: ./a.out ==327== Allocated 1024 bytes at 0x522f040 malloc leak count=1 malloc leak count=2 malloc leak count=3 ^CReceived signal 2, exiting... ==327== ==327== HEAP SUMMARY: ==327== in use at exit: 4,096 bytes in 4 blocks ==327== total heap usage: 5 allocs, 1 frees, 5,120 bytes allocated ==327== ==327== 1,024 bytes in 1 blocks are still reachable in loss record 1 of 3 ==327== at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==327== by 0x1087C6: main (memcheck.c:20) ==327== ==327== 1,024 bytes in 1 blocks are still reachable in loss record 2 of 3 ==327== at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==327== by 0x1087FC: main (memcheck.c:26) ==327== ==327== 2,048 bytes in 2 blocks are definitely lost in loss record 3 of 3 ==327== at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==327== by 0x1087FC: main (memcheck.c:26) ==327== ==327== LEAK SUMMARY: ==327== definitely lost: 2,048 bytes in 2 blocks ==327== indirectly lost: 0 bytes in 0 blocks ==327== possibly lost: 0 bytes in 0 blocks ==327== still reachable: 2,048 bytes in 2 blocks ==327== suppressed: 0 bytes in 0 blocks ==327== ==327== For counts of detected and suppressed errors, rerun with: -v ==327== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
definitely lost: 2,048 bytes in 2 blocks,确定的是2次的内存泄露,这是因为p在malloc后,再一次被新的malloc赋值,上一次的内存就一定丢失了。
still reachable: 2,048 bytes in 2 blocks,global_buf和第三次的p,在程序退出的时候,还可以通过指针访问,因此认为是无害泄露,但是第三次p也算是泄露。
==327== HEAP SUMMARY: ==327== in use at exit: 4,096 bytes in 4 blocks # 退出时仍有 4 块内存未释放 ==327== total heap usage: 5 allocs, 1 frees, 5,120 bytes allocated # 共分配 5 次,仅释放 1 次
通过log可以看到我们实际是malloc了4次,为什么上面说是分配5次,释放1次。
多的一次是printf内部的malloc以及程序退出后自动释放了。
2.dmalloc
内存占用:dmalloc 通过替换 malloc/free
等函数实现内存跟踪,需维护空闲链表和分配记录。其内存开销包括:
堆管理开销:每个分配块需额外存储元数据(如大小、文件名、行号),通常增加 8-16 字节/块。
日志缓冲区:默认日志可能占用数 KB 到 MB 级内存(可通过配置限制)。
适用场景:中等资源设备(如 RAM 16MB-64MB 的嵌入式 Linux),适合长期运行的服务监控。
优化建议:
禁用调试信息:编译时去掉 -g
选项,减少日志体积。
静态链接:避免动态库加载带来的额外内存占用。
局限性:
- 无法监控静态分配:如全局变量、栈内存。
- 自定义分配器:若库完全绕过标准内存管理,
dmalloc
无效。 - 性能影响:详细日志可能导致程序运行变慢。
dmalloc编译成库,集成到需要定位内存泄露的应用程序中。
demo:

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <signal.h> 6 #include <dmalloc.h> // dmalloc 头文件 7 8 // 模拟内存泄漏的函数 9 void leak_memory() { 10 char *leak_buf = (char *)malloc(1024); // 故意不释放 11 if (leak_buf) { 12 strcpy(leak_buf, "This memory will leak!"); 13 } 14 } 15 16 // 模拟正常内存操作 17 void normal_memory_op() { 18 char *buf = (char *)malloc(256); 19 if (buf) { 20 strcpy(buf, "This memory will be freed."); 21 free(buf); // 正常释放 22 } 23 } 24 25 // 信号处理函数:触发 dmalloc 泄漏检查 26 void dump_leaks(int sig) { 27 printf("\n[SIGNAL RECEIVED] Dumping unfreed memory...\n"); 28 dmalloc_log_unfreed(); // 输出泄漏信息到 dmalloc 日志 29 printf("[DONE] Check dmalloc.log for details.\n"); 30 } 31 32 int main() { 33 // 1. 初始化 dmalloc 34 dmalloc_debug(0x4f47d03); // 启用详细调试(见前文解释) 35 36 // 2. 设置信号处理(SIGUSR1 用于手动触发泄漏检查) 37 signal(SIGUSR1, dump_leaks); 38 39 printf("dmalloc memory leak demo started (PID: %d)\n", getpid()); 40 printf("Send SIGUSR1 to dump leaks: kill -USR1 %d\n", getpid()); 41 42 // 3. 模拟长期运行的程序 43 while (1) { 44 leak_memory(); // 模拟泄漏 45 normal_memory_op(); // 模拟正常操作 46 sleep(5); // 每 5 秒循环一次 47 } 48 49 return 0; // 不会执行到这里 50 }
设置dmalloc环境变量:
export DMALLOC_OPTIONS="log=dmalloc.log,debug=0x4f47d03,inter=1"
log=dmalloc.log
:日志输出到当前目录的dmalloc.log
。debug=0x4f47d03
:启用详细调试(见前文解释)。inter=1
:允许交互式调试(如通过信号触发)
手动发送信号:
kill -USR1 $(pidof dmalloc_demo)
查看日志:
cat dmalloc.log [dmalloc] Leaked memory at 0x55a1b2b3c4d5 (1024 bytes), allocated at dmalloc_demo.c:10 [dmalloc] Leaked memory at 0x55a1b2b3c8e9 (1024 bytes), allocated at dmalloc_demo.c:10
3.mtrace
- 内存占用:mtrace 是 GNU C 库提供的轻量级工具,通过环境变量
MALLOC_TRACE
记录内存操作到文件。其内存开销极小:- 运行时开销:仅需少量内存存储日志文件路径和缓冲区(通常 < 1KB)。
- 日志文件:存储在磁盘(或嵌入式设备的 Flash),不占用 RAM。
- 适用场景:所有嵌入式设备(包括 MCU),尤其适合资源敏感型场景。
- 优化建议:
- 定期清理日志:避免日志文件过大占用存储空间。
- 使用二进制日志格式:减少解析开销(如通过
mtrace
工具转换日志)。
- 检测未释放的内存:能快速定位
malloc()
但未free()
的情况。 - 适合 C 程序:对 C++ 的
new
/delete
支持有限(需结合malloc
钩子)。
demo:

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <signal.h> 6 #include <mcheck.h> // mtrace 头文件 7 8 // 模拟内存泄漏的函数 9 void leak_memory() { 10 char *leak_buf = (char *)malloc(1024); // 故意不释放 11 if (leak_buf) { 12 strcpy(leak_buf, "This memory will leak!"); 13 } 14 } 15 16 // 模拟正常内存操作 17 void normal_memory_op() { 18 char *buf = (char *)malloc(256); 19 if (buf) { 20 strcpy(buf, "This memory will be freed."); 21 free(buf); // 正常释放 22 } 23 } 24 25 // 信号处理函数:触发 mtrace 泄漏检查 26 void dump_leaks(int sig) { 27 printf("\n[SIGNAL RECEIVED] Checking memory leaks...\n"); 28 mtrace(); // 强制输出当前未释放的内存到日志文件 29 printf("[DONE] Check /var/log/mtrace.log for details.\n"); 30 } 31 32 int main() { 33 // 1. 启用 mtrace 34 setenv("MALLOC_TRACE", "/var/log/mtrace.log", 1); // 设置日志路径 35 mtrace(); // 初始化 mtrace 36 37 // 2. 设置信号处理(SIGUSR1 用于手动触发泄漏检查) 38 signal(SIGUSR1, dump_leaks); 39 40 printf("mtrace memory leak demo started (PID: %d)\n", getpid()); 41 printf("Send SIGUSR1 to dump leaks: kill -USR1 %d\n", getpid()); 42 43 // 3. 模拟长期运行的程序 44 while (1) { 45 leak_memory(); // 模拟泄漏 46 normal_memory_op(); // 模拟正常操作 47 sleep(5); // 每 5 秒循环一次 48 } 49 50 return 0; // 不会执行到这里 51 }
编译:
gcc -o mtrace_demo mtrace_demo.c
mtrace
是 glibc 的一部分,无需额外链接库。
备注:
- glibc是Linux生态的基石,功能全面但体积较大,适合通用Linux系统。
- uClibc通过精简功能和优化体积,成为嵌入式领域的优选,但需注意其功能局限性和兼容性问题。
创建日志目录:
sudo mkdir -p /var/log sudo chown $USER /var/log/mtrace.log # 确保程序有权限写入
手动发送信号:
kill -USR1 $(pidof mtrace_demo)
查看日志:
cat /var/log/mtrace.log = Start @ ./mtrace_demo[/path/to/mtrace_demo.c:10] 0x55a1b2b3c4d5 1024 @ ./mtrace_demo[/path/to/mtrace_demo.c:10] 0x55a1b2b3c8e9 1024
- 每行格式:
@ [分配位置] 地址 大小
。