Linux 基于valgrind分析内存泄漏

Linux 基于valgrind分析内存泄漏 2024-09-29 16:43:09 114

1.valgrind工具

通过以下命令查看valgrind工具是否安装,以及安装的版本:

zl@zl-VirtualBox:~/vstdio-workspace/memoryout-test/build$ valgrind --version
valgrind-3.13.0
zl@zl-VirtualBox:~/vstdio-workspace/memoryout-test/build$

如果提示命令没找到,那可能是环境变量不对,或者是没有安装,x86的Linux系统上安装都比较简单,直接通过命令就可以;arm Linux上可能需要重新编译文件系统或者是交叉编译valgrind源码包。

2.命令

valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --undef-value-errors=no --log-file=./memory-test_valgrind_report-1.log ./memory-test

2.1命令分析

  • —tool=memcheck: 使用 memcheck 工具检测内存错误,包括使用未初始化的变量、读写越界等。
  • —leak-check=full: 全面检测内存泄漏,不仅仅检测未释放的内存,还会检测处理时出现的一些问题。
  • —show-leak-kinds=all: 显示所有的内存泄漏信息。
  • —undef-value-errors=no: 不检查未定义的值错误。
  • —log-file=./test.log: 将日志信息输出到 log 文件中。
  • ./memory-test:要测试的可执行文件

执行完之后我们便可以看到在文件夹内生成一个名为test.log的文件,文件中便是内存信息。

2.2输出日志说明

比如,我们检测内存泄漏的时候拿到了如下内容的log日志:

==6048== Memcheck, a memory error detector
==6048== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6048== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==6048== Command: ./memoryout-test
==6048== Parent PID: 2379
==6048== 
==6048== 
==6048== HEAP SUMMARY:
==6048==     in use at exit: 1,024 bytes in 1 blocks
==6048==   total heap usage: 1 allocs, 0 frees, 1,024 bytes allocated
==6048== 
==6048== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1
==6048==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6048==    by 0x108735: main (memoryout-test.c:33)
==6048== 
==6048== LEAK SUMMARY:
==6048==    definitely lost: 1,024 bytes in 1 blocks
==6048==    indirectly lost: 0 bytes in 0 blocks
==6048==      possibly lost: 0 bytes in 0 blocks
==6048==    still reachable: 0 bytes in 0 blocks
==6048==         suppressed: 0 bytes in 0 blocks
==6048== 
==6048== For counts of detected and suppressed errors, rerun with: -v
==6048== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

分析每一行的含义:

==6048== Memcheck, a memory error detector

这句话是 Valgrind 内存检查器的标识。

==6048== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.

这是版权信息,显示了 Valgrind 的作者和版权信息。

==6048== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info

这句话告诉您正在使用哪个版本的 Valgrind 和哪个库(LibVEX)。

==6048== Command: ./memoryout-test

这句话显示了启动程序的命令。

==6048== Parent PID: 2379

这句话显示了父进程的进程 ID。

==6048== 
==6048==

这是空行,用于分割段落。

==6048== HEAP SUMMARY:

这句话开始了对堆内存的摘要。

==6048==     in use at exit: 1,024 bytes in 1 blocks

这句话显示在程序退出时还有多少字节的堆内存处于使用状态。

==6048==   total heap usage: 1 allocs, 0 frees, 1,024 bytes allocated

这句话显示了程序总共分配了多少字节的堆内存,包括其中被释放的和未被释放的。

==6048==

这又是一个空行,用于分割段落。

==6048== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1
==6048==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6048==    by 0x108735: main (memoryout-test.c:33)

这句话告诉您哪个内存块被泄漏,并提供了分配该内存块的函数和文件。在这个例子中,它显示了在程序的第33行中使用了 malloc 来分配一个内存块,在程序退出时未释放该内存块。

==6048==

这又是一个空行,用于分割段落。

==6048== LEAK SUMMARY:

这句话开始了对泄漏情况的摘要。

==6048==    definitely lost: 1,024 bytes in 1 blocks

这句话指出了存在明确的内存泄漏,即分配的 1024 字节内存块在程序退出时未被释放。

==6048==    indirectly lost: 0 bytes in 0 blocks

这句话指出了间接的内存泄漏,即分配的内存块的指针丢失了,程序无法再次释放该内存块。

==6048==      possibly lost: 0 bytes in 0 blocks

这句话指出可能存在泄漏,但是 Valgrind 无法确定。

==6048==    still reachable: 0 bytes in 0 blocks

这句话显示程序仍然可以访问的内存块的大小,即仍处于程序控制下的内存块。

==6048==         suppressed: 0 bytes in 0 blocks

这句话显示已被抑制的错误和泄漏数量。

==6048==

这又是一个空行,用于分割段落。

==6048== For counts of detected and suppressed errors, rerun with: -v

这句话提供有关检测到的错误和被抑制的错误数量的信息。如果需要更详细的信息,请使用 -v 参数再次运行 Valgrind。

==6048== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

这句话告诉您在多少上下文中检测到了多少个错误,在本例中,检测到了1个内存泄漏的错误。

3.内存泄漏

valgrind 将内存泄漏分为 4 类:

  • 明确泄漏(definitely lost):内存还没释放,但已经没有指针指向内存,内存已经不可访问。
  • 间接泄漏(indirectly lost):泄漏内存指针保存在明确泄漏内存中,随着明确泄漏内存不可访问,导致间接泄漏内存也不可访问。
  • 可能泄漏(possibly lost):指针并不指向内存头地址,而是指向内存内部的位置。
  • 仍可访达(still reachable):指针一直存在且指向内存头部,直至程序退出时内存还没释放。

下面详细看下每一类泄漏的实例以及抓取到的日子log:

3.1明确泄漏(definitely lost)

1.实例源码:

int main(int argc, char **argv)
{
    char *buf = NULL;
    buf = (char *)malloc(1024);
    if (buf == NULL)
    {
        perror("malloc failed");
    }

    return 0;
}

2.valgrind抓取的日志:

==26343== Memcheck, a memory error detector
==26343== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==26343== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==26343== Command: ./memoryout-test
==26343== Parent PID: 26233
==26343== 
==26343== 
==26343== HEAP SUMMARY:
==26343==     in use at exit: 1,024 bytes in 1 blocks
==26343==   total heap usage: 1 allocs, 0 frees, 1,024 bytes allocated
==26343== 
==26343== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1
==26343==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==26343==    by 0x1086AA: main (memoryout-test.c:10)
==26343== 
==26343== LEAK SUMMARY:
==26343==    definitely lost: 1,024 bytes in 1 blocks
==26343==    indirectly lost: 0 bytes in 0 blocks
==26343==      possibly lost: 0 bytes in 0 blocks
==26343==    still reachable: 0 bytes in 0 blocks
==26343==         suppressed: 0 bytes in 0 blocks
==26343== 
==26343== For counts of detected and suppressed errors, rerun with: -v
==26343== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

3.分析:

该类泄漏,程序已经失去了对申请的内存的操作能力,所以这类是必须要改掉的。

3.2间接泄漏(indirectly lost)

1.实例源码:

struct people
{
    char *name;
};

int main(int argc, char **argv)
{
    struct people *one;
    one = (struct people *)malloc(sizeof(struct people));
    if (one == NULL)
    {
        perror("malloc failed");
    }

    one->name = (char *)malloc(64);
    if (one->name == NULL)
    {
        perror("malloc failed");
    }

    one = NULL;

    return 0;
}

2.valgrind抓取的日志:

==31693== Memcheck, a memory error detector
==31693== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==31693== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==31693== Command: ./memoryout-test
==31693== Parent PID: 26233
==31693== 
==31693== 
==31693== HEAP SUMMARY:
==31693==     in use at exit: 72 bytes in 2 blocks
==31693==   total heap usage: 2 allocs, 0 frees, 72 bytes allocated
==31693== 
==31693== 64 bytes in 1 blocks are indirectly lost in loss record 1 of 2
==31693==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==31693==    by 0x1086C3: main (memoryout-test.c:21)
==31693== 
==31693== 72 (8 direct, 64 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==31693==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==31693==    by 0x1086A2: main (memoryout-test.c:15)
==31693== 
==31693== LEAK SUMMARY:
==31693==    definitely lost: 8 bytes in 1 blocks
==31693==    indirectly lost: 64 bytes in 1 blocks
==31693==      possibly lost: 0 bytes in 0 blocks
==31693==    still reachable: 0 bytes in 0 blocks
==31693==         suppressed: 0 bytes in 0 blocks
==31693== 
==31693== For counts of detected and suppressed errors, rerun with: -v
==31693== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

3.分析:

one指针变量直接置空,导致申请的8 bytes大小的内存确定泄漏,也就是这个是明确泄漏。而one的置空,导致了name赋值的64 bytes的内存也无法访问了,所以这64bytes是间接泄漏的。间接泄漏内存也得修改,但是优先修改导致其泄漏的明确泄漏。

3.3可能泄漏(possibly lost)

1.实例源码:

char *buffer = NULL;
int main(int argc, char **argv)
{
    buffer = (char *)malloc(1024);
    if (buffer == NULL)
    {
        perror("malloc failed");
    }

    buffer++;

    return 0;
}

2.valgrind抓取的日志:

==8937== Memcheck, a memory error detector
==8937== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8937== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==8937== Command: ./memoryout-test
==8937== Parent PID: 26233
==8937== 
==8937== 
==8937== HEAP SUMMARY:
==8937==     in use at exit: 1,024 bytes in 1 blocks
==8937==   total heap usage: 1 allocs, 0 frees, 1,024 bytes allocated
==8937== 
==8937== 1,024 bytes in 1 blocks are possibly lost in loss record 1 of 1
==8937==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==8937==    by 0x1086A2: main (memoryout-test.c:54)
==8937== 
==8937== LEAK SUMMARY:
==8937==    definitely lost: 0 bytes in 0 blocks
==8937==    indirectly lost: 0 bytes in 0 blocks
==8937==      possibly lost: 1,024 bytes in 1 blocks
==8937==    still reachable: 0 bytes in 0 blocks
==8937==         suppressed: 0 bytes in 0 blocks
==8937== 
==8937== For counts of detected and suppressed errors, rerun with: -v
==8937== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

3.分析:

valgrind 之所以会怀疑可能泄漏,是因为指针已经偏移,并没有指向内存头,而是有内存偏移,指向内存内部的位置。 可能泄漏需要具体分析,比如实例中是因为使用了++操作,确认不是功能需要的修改之后,重新修改,指向内存头,然后再进一步分析。

3.4仍可访达(still reachable)

1.实例源码:

char *buffer = NULL;
int main(int argc, char **argv)
{
    buffer = (char *)malloc(1024);
    if (buffer == NULL)
    {
        perror("malloc failed");
    }

    return 0;
}

2.valgrind抓取的日志:

==10960== Memcheck, a memory error detector
==10960== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10960== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10960== Command: ./memoryout-test
==10960== Parent PID: 26233
==10960== 
==10960== 
==10960== HEAP SUMMARY:
==10960==     in use at exit: 1,024 bytes in 1 blocks
==10960==   total heap usage: 1 allocs, 0 frees, 1,024 bytes allocated
==10960== 
==10960== 1,024 bytes in 1 blocks are still reachable in loss record 1 of 1
==10960==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==10960==    by 0x1086A2: main (memoryout-test.c:71)
==10960== 
==10960== LEAK SUMMARY:
==10960==    definitely lost: 0 bytes in 0 blocks
==10960==    indirectly lost: 0 bytes in 0 blocks
==10960==      possibly lost: 0 bytes in 0 blocks
==10960==    still reachable: 1,024 bytes in 1 blocks
==10960==         suppressed: 0 bytes in 0 blocks
==10960== 
==10960== For counts of detected and suppressed errors, rerun with: -v
==10960== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

3.分析:

仍可访达 表示在程序退出时,不管是正常退出还是异常退出,内存申请了没释放,都属于仍可访达的泄漏类型。

如果测试的程序是正常退出的,那么这些仍可访达的内存就是泄漏,最好修复了。

如果测试是长期运行的程序,通过信号提前终止,那么这些内存就大概率并不是泄漏。

所以说这种情况也需要具体分析一下,比如上面的实例,是正常的退出,程序已经执行完,这个时候仍可访达也需要修改,手动释放掉。如果是一个线程,长期运行,为了抓取valgrind日志才通过信号终止的,这个时候根据日志确定没有释放的内存是正常情况,还是异常情况。

4.总结

如果通过valgrind抓取的日志中,出现了四类内存泄漏中的任何一种,都应该引起重视,根据日志中显示的详细信息,分析出现的内存泄漏是属于正常情况,也就是功能需要,还是异常情况,需要修改掉。另外,一定要注意,并不是valgrind抓取的日志中没有任何泄漏就说明是正常的,还需要结合程序实际运行的情况,以其他工具辅助,比如top,分析程序是不是在不断申请内存。因为有时候valgrind抓取的日志是通过信号中止了线程之后抓取的,这个时候可能隐藏程序运行过程中的问题,比如:我就碰到一种情况,烧机借助top发现程序占的内存在不断增加,直到耗尽设备内存被kill掉,但是我通过信号中止程序抓取的log中并没有提示内存泄漏,最后排查是信号中止之后线程退出,在退出后面的处理中,将线程内申请的可访达的内存都释放掉了,所以导致抓取不到内存使用异常。

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区