如何检查大型c++ Linux应用程序中的内存泄漏

How to check for memory leaks in a large scale c++ Linux application?

本文关键字:应用程序 Linux 泄漏 内存 c++ 大型 何检查 检查      更新时间:2023-10-16

我目前正在从事一个大型应用程序项目(用c++编写),这个项目是前段时间从零开始的,我们达到了必须对内存泄漏进行综合检查的地步。

该应用程序运行在Ubuntu Linux上,它有很多多媒体内容,并使用OpenGl, SDL和ffmpeg用于各种目的,包括3D图形渲染,窗口,音频和电影播放。你可以把它想象成一个电子游戏,虽然它不是,但是应用程序的职责可以通过把它看作一个电子游戏来简化。

我目前在确定我们是否仍然有内存泄漏方面有点无能为力。在过去,我们已经识别了一些,并删除了它们。这些天,应用程序几乎完成了,我们运行的测试给我的结果我不能确切地弄清楚。

我做的第一件事是试着通过Valgrind运行应用程序…不幸的是,应用程序在valgrind环境中运行时会崩溃。崩溃是"不确定的",因为它在不同的地方崩溃。因此,我放弃了使用Valgrind来轻松识别潜在泄漏的来源,最终使用了两个Linux命令:free和top。

free用于在应用程序运行时探测系统内存使用情况

top与'-p'选项一起使用,在运行时探测应用程序进程的内存使用情况。

输出表单top和free被转储到文件中进行后处理。我用问题底部的数据做了两个图表。

测试用例非常简单:一旦应用程序已经启动并且正在等待命令,就会探测有关内存的数据。然后我开始一个命令序列,它总是重复做同样的事情。应用程序需要将大量多媒体数据加载到RAM中,然后再下载。

不幸的是,图表没有显示我所期望的。内存使用通过3个不同的步骤增长,然后停止。内存显然从未被释放,这暗示我有一个巨大的内存泄漏。这完全没问题,因为这意味着我们很可能不会释放被媒体内容占用的内存。

但是在前三步之后…内存使用稳定…再也没有大的步伐了……只是轻微的上下,对应于预期的数据加载和卸载。意想不到的是,应该被加载/卸载的数据占用了百分之几兆字节的RAM,而不是上升和下降只占用了几个兆字节(比如8-10 MB)。

我现在对这些数据的解释很无能。

有人有什么提示或建议吗?我错过了什么?我用于检查宏观内存泄漏是否存在的方法是否完全错误?除了Valgrind之外,你还知道其他(最好是免费的)检查内存泄漏的工具吗?

系统内存占用率图

进程内存使用图

与其放弃Valgrind,不如使用它们并尝试

  • 删除你在Valgrind中遇到的bug
  • 让你的应用程序彻底测试和调试更新的Valgrind。

说你放弃了Valgrind 这个解决你问题的方法真的没有帮助…

Valgrind是我们在linux下用来检查内存泄漏和线程问题的工具。

最后,比起寻找替代解决方案,花时间弄清楚"为什么Valgrind不能与我的应用一起工作"无疑是更好的选择。Valgrind是一个经过验证和测试的工具,但并不完美。而且它比其他方法要好得多。

Valgrind页面说最好将bug提交给Bugzilla,但实际上最好在https://lists.sourceforge.net/lists/listinfo/valgrind-users上询问是否有人以前看到过这样的问题以及在这种情况下该怎么做。最坏的情况——他们会让你把bug提交给bugzilla或者他们自己提交。

首先…

,我们达到了强制对内存泄漏进行综合检查的地步。

这实际上是一个方法论问题。正确性应该是任何软件的首要目标,而不是事后考虑。

我假设您现在已经意识到这一点,并且如果您在每次提交时都运行一个仪表化的单元测试,那么识别问题将会容易得多。


那么,现在该怎么办呢?

    运行时检测:
    • 尝试让Valgrind工作,你可能有一些环境问题
    • 尝试ASan, ThreadSan和MemSan;在Linux下设置它们并不简单,但是令人印象深刻!
    • 尝试仪表构建:tcmalloc包含堆检查器,例如
  • 编译时间检测:

    • 打开警告(最好是-Werror)(不是针对您的问题)
    • 使用静态分析,例如Clang的,它可能会发现未配对的分配例程
  • 人类检测:
    • 代码审查:确保所有资源分配在RAII类

注意:仅使用RAII类有助于消除内存泄漏,但无助于悬空引用。值得庆幸的是,检测悬空引用正是ASan所做的。


一旦你修补了所有的问题,确保这成为过程的一部分。应该总是对更改进行审查和测试,以便立即剔除坏鸡蛋,而不是让它们弄脏代码库。

您可能想要查看valgrind。

你可能想从一些非常简单的例子开始,来感受一下valgrind报告了什么,这可能有点冗长。考虑这个简化的示例,其中valgrind确切地丢失了什么和多少:

edd@max:/tmp$ cat valgrindex.cpp 
#include <cstdlib>
int main() {
  double *a = new double[100];
  exit(0);
}
edd@max:/tmp$ g++ -o valgrindex valgrindex.cpp 
edd@max:/tmp$ valgrind ./valgrindex
==15910== Memcheck, a memory error detector
==15910== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==15910== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==15910== Command: ./valgrindex
==15910== 
==15910== 
==15910== HEAP SUMMARY:
==15910==     in use at exit: 800 bytes in 1 blocks
==15910==   total heap usage: 1 allocs, 0 frees, 800 bytes allocated
==15910== 
==15910== LEAK SUMMARY:
==15910==    definitely lost: 0 bytes in 0 blocks
==15910==    indirectly lost: 0 bytes in 0 blocks
==15910==      possibly lost: 0 bytes in 0 blocks
==15910==    still reachable: 800 bytes in 1 blocks
==15910==         suppressed: 0 bytes in 0 blocks
==15910== Rerun with --leak-check=full to see details of leaked memory
==15910== 
==15910== For counts of detected and suppressed errors, rerun with: -v
==15910== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
edd@max:/tmp$ 

freetop的结果对您没有帮助。我很遗憾你为了他们的结果花了很多精力来构造图表。在一个类似的主题中,我已经很好地解释了为什么它们没有帮助:Linux中c++应用程序的内存稳定性。

我也同意这里的其他答案,您可能应该优先排除您在Valgrind中遇到的崩溃。Valgrind在这一点上被认为是非常稳定的,我个人也运行过相当复杂的多线程多媒体SDL/OpenGL等。应用程序通过它没有问题。更有可能的是,Valgrind的运行环境暴露了应用程序中可能存在的不稳定性。这个崩溃听起来像线程竞争条件崩溃,尽管它也可能是堆/内存损坏。

你可能想问,然后,是关于如何调试一个应用程序,从Valgrind的运行环境崩溃的建议(这是我不知道答案的东西)。

free和top的问题是它们可以向您显示问题,但它们在解决问题方面提供的帮助很少。在分配内存的100或1000行代码中,哪些是泄漏的?这就是valgrind的作用所在。

如果这是一个有工具预算的公司,您可能会考虑purify或其他商业工具。

为了完整起见,我将提到Boehm保守垃圾收集内存分配器(它适用于C和c++代码)。您可以关闭GC并使用GC_Free(),它将成为泄漏检测工具。或者您可以让GC处于启用状态,以便在不再使用内存时自动释放内存。

这完全取决于您使用的是哪个分配器。libc分配器(malloc、calloc、realloc)和c++分配器(new、delete)可能使用了不将内存释放回操作系统的优化技巧。您看,如果您向malloc请求一些内存,使用它,然后释放它,它不一定会被释放回操作系统。相反,如果我向malloc请求内存,它(大多数情况下)会获得比需要的更多的内存(因为页面边界)。这样,下次你需要更多内存时,malloc就会把它放在那里。免费也是一样。内存可能只是添加到malloc内存池中,稍后的分配将从中提取。

所以,您的应用程序最初的几个malloc占用内存非常高,但随后池足够大以容纳未来的分配。

除了使用valgrind,您还可以考虑使用Boehm的保守GC;您可能希望编译它并将其配置为内存泄漏检测器。

你甚至可以使用Boehm的GC作为主内存分配器。

BTW,查看/proc/1234/maps可以帮助您(其中1234是您的进程的pid)。