在 Linux 平台中调试 C/C++ 内存泄漏方法

来源:互联网 发布:风撑规格算法 编辑:程序博客网 时间:2024/05/16 05:48

在 Linux 平台中调试 C/C++ 内存泄漏方法

转自:http://www.ibm.com/developerworks/cn/linux/l-cn-memleak/index.html

由于 C 和 C++ 程序中完全由程序员自主申请和释放内存,稍不注意,就会在系统中导入内存错误。同时,内存错误往往非常严重,一般会带来诸如系统崩溃,内存耗尽这样严重的后果。本文将从静态分析和动态检测两个角度介绍在 Linux 环境进行内存泄漏检测的方法,并重点介绍静态分析工具 BEAM、动态监测工具 Valgrind 和 rational purify 的使用方法。相信通过本文的介绍,能给大家对处理其它产品或项目内存泄漏相关的问题时提供借鉴。


由于 C 和 C++ 程序中完全由程序员自主申请和释放内存,稍不注意,就会在系统中导入内存错误。同时,内存错误往往非常严重,一般会带来诸如系统崩溃,内存耗尽这样严重的后果。从历史上看,来自计算机应急响应小组和供应商的许多最严重的安全公告都是由简单的内存错误造成的。自从 70 年代末期以来,C/C++ 程序员就一直讨论此类错误,但其影响在 2007 年仍然很大。与许多其他类型的常见错误不同,内存错误通常具有隐蔽性,即它们很难再现,症状通常不能在相应的源代码中找到。例如,无论何时何地发生内存泄漏,都可能表现为应用程序完全无法接受,同时内存泄漏不是显而易见[1]。存在内存错误的 C 和 C++ 程序会导致各种问题。如果它们泄漏内存,则运行速度会逐渐变慢,并最终停止运行;如果覆盖内存,则会变得非常脆弱,很容易受到恶意用户的攻击。

因此,出于这些原因,需要特别关注 C 和 C++ 编程的内存问题,特别是内存泄漏。本文先从如何发现内存泄漏,然后是用不同的方法和工具定位内存泄漏,最后对这些工具进行了比较,另外还简单介绍了资源泄漏的处理(以句柄泄漏为例)。本文使用的测试平台是:Linux (Redhat AS4)。但是这些方法和工具许多都不只是局限于 C/C++ 语言以及 linux 操作系统。

内存泄漏一般指的是堆内存的泄漏。堆内存是指程序从堆中分配的、大小任意的(内存块的大小可以在程序运行期决定)、使用完后必须显示的释放的内存。应用程序一般使用malloc、realloc、new 等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用 free 或 delete 释放该内存块。否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

1. 如何发现内存泄漏

有些简单的内存泄漏问题可以从在代码的检查阶段确定。还有些泄漏比较严重的,即在很短的时间内导致程序或系统崩溃,或者系统报告没有足够内存,也比较容易发现。最困难的就是泄漏比较缓慢,需要观测几天、几周甚至几个月才能看到明显异常现象。那么如何在比较短的时间内检测出有没有潜在的内存泄漏问题呢?实际上不同的系统都带有内存监视工具,我们可以从监视工具收集一段时间内的堆栈内存信息,观测增长趋势,来确定是否有内存泄漏。在 Linux 平台可以用 ps 命令,来监视内存的使用,比如下面的命令 (观测指定进程的VSZ值):

ps -aux

2. 静态分析

包括手动检测和静态工具分析,这是代价最小的调试方法。

2.1 手动检测

当使用 C/C++ 进行开发时,采用良好的一致的编程规范是防止内存问题第一道也是最重要的措施。检测是编码标准的补充。二者各有裨益,但结合使用效果特别好。专业的 C 或 C++ 专业人员甚至可以浏览不熟悉的源代码,并以极低的成本检测内存问题。通过少量的实践和适当的文本搜索,您能够快速验证平衡的 *alloc() 和 free() 或者 new 和 delete 的源主体。人工查看此类内容通常会出现像清单1 中一样的问题,可以定位出在函数 LeakTest 中的堆变量 Logmsg 没有释放。

清单1. 简单的内存泄漏
#include <stdio.h>#include <string.h>#include <stdlib.h>int LeakTest(char * Para){        if(NULL==Para){                //local_log("LeakTest Func: empty parameter\n");                return -1;        }        char * Logmsg = new char[128];        if(NULL == Logmsg){                //local_log("memeory allocation failed\n");                return -2;        }        sprintf(Logmsg,"LeakTest routine exit: '%s'.\n", Para);        //local_log(Logmsg);        return 0;}int   main(int argc,char **argv ){        char szInit [] = "testcase1";        LeakTest(szInit);        return 0;}

2.2 静态代码分析工具

代码静态扫描和分析的工具比较多,比如 splint, PC-LINT, BEAM 等。因为 BEAM 支持的平台比较多,这以 BEAM 为例,做个简单介绍,其它有类似的处理过程。

BEAM 可以检测四类问题: 没有初始化的变量;废弃的空指针;内存泄漏;冗余计算。而且支持的平台比较多。

BEAM 支持以下平台:

  • Linux x86 (glibc 2.2.4)
  • Linux s390/s390x (glibc 2.3.3 or higher)
  • Linux (PowerPC, USS) (glibc 2.3.2 or higher)
  • AIX (4.3.2+)
  • Window2000 以上
清单2. 用作 Beam 分析的代码
#include <stdio.h>#include <string.h>#include <stdlib.h>int *p;voidfoo(int a){  int b, c;  b = 0;  if(!p)     c = 1;  if(c > a)    c += p[1];}int LeakTest(char * Para){        char * Logmsg = new char[128];        if((Para==NULL)||(Logmsg == NULL))                return -1;                sprintf(Logmsg,"LeakTest routine exit: '%s'.\n", Para);                return 0;}int   main(int argc,char **argv ){        char szInit [] = "testcase1";        LeakTest(szInit);        return 0;}

下面以 X86 Linux 为例,代码如清单 2,具体的环境如下:

OS: Red Hat Enterprise Linux AS release 4 (Nahant Update 2)

GCC: gcc version 3.4.4

BEAM: 3.4.2; https://w3.eda.ibm.com/beam/

可以把 BEAM 看作一个 C/C++ 编译器,按下面的命令进行编译 (前面两个命令是设置编译器环境变量):

./beam-3.4.2/bin/beam_configure  --c gcc./beam-3.4.2/bin/beam_configure  --cpp g++./beam-3.4.2/bin/beam_compile  --beam::compiler=compiler_cpp_config.tcl  -cpp code2.cpp

从下面的编译报告中,我们可以看到这段程序中有三个错误:”内存泄漏”;“变量未初始化”;“ 空指针操作”

"code2.cpp", line 10: warning: variable "b" was set but never used    int b, c;        ^BEAM_VERSION=3.4.2BEAM_ROOT=/home/hanzb/memdetectBEAM_DIRECTORY_WRITE_INNOCENTS=BEAM_DIRECTORY_WRITE_ERRORS=-- ERROR23(heap_memory)     /*memory leak*/     >>>ERROR23_LeakTest_7b00071dc5cbb458"code2.cpp", line 24: memory leakONE POSSIBLE PATH LEADING TO THE ERROR: "code2.cpp", line 22: allocating using `operator new[]' (this memory will not be freed) "code2.cpp", line 22: assigning into `Logmsg' "code2.cpp", line 24: deallocating `Logmsg' because exiting its scope                        (losing last pointer to the memory)-- ERROR1     /*uninitialized*/     >>>ERROR1_foo_60c7889b2b608"code2.cpp", line 16: uninitialized `c'ONE POSSIBLE PATH LEADING TO THE ERROR: "code2.cpp", line 10: allocating `c' "code2.cpp", line 13: the if-condition is false "code2.cpp", line 16: getting the value of `c' VALUES AT THE END OF THE PATH:  p != 0 -- ERROR2     /*operating on NULL*/     >>>ERROR2_foo_af57809a2b615"code2.cpp", line 17: invalid operation involving NULL pointerONE POSSIBLE PATH LEADING TO THE ERROR: "code2.cpp", line 13: the if-condition is true (used as evidence that error is possible) "code2.cpp", line 16: the if-condition is true "code2.cpp", line 17: invalid operation `[]' involving NULL pointer `p' VALUES AT THE END OF THE PATH:  c = 1   p = 0   a <= 0

2.3 内嵌程序

可以重载内存分配和释放函数 new 和 delete,然后编写程序定期统计内存的分配和释放,从中找出可能的内存泄漏。或者调用系统函数定期监视程序堆的大小,关键要确定堆的增长是泄漏而不是合理的内存使用。这类方法比较复杂,在这就不给出详细例子了。

图 1 purify 的输出结果

结论

本文介绍了多种内存泄漏,定位方法(包括静态分析,动态实时检测)。涉及到了多个工具,详细描述的它们的用法、用途以及优缺点。对处理其它产品或项目内存泄漏相关的问题有很好的借鉴意义。

参考资料

学习

  • 内存调试技巧一文介绍了内存错误的分类和危害性,并且给出了一些良好的和内存相关的编码实践。
  • 实时内存检测工具 Virgrind 的介绍和使用方法,可以参考 Virgrind 主页
  • 有关 Rational Purify 的使用方法,可以参考 Rational Purify 使用及分析实例。
  • 内存管理内幕一文介绍了linux内存管理的机制,希望对大家实地操作有所启发。
  • 访问 developerWorks Linux 专区,查找面向 Linux 开发人员的更多参考资料,并阅读最受欢迎的文章和教程。
  • 查看 developerWorks 上所有的 Linux 技巧和 Linux 教程。
  • 随时关注 developerWorks 技术活动和网络广播。 

0 0
原创粉丝点击