Linux内核开发  - 讨论区

标题:在 Linux下调试内存泄漏的方法(1)

2011年01月17日 星期一 08:52

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

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

1. 如何发现内存泄漏

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

ps -aux

2. 静态分析

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

(1)手动检测

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

清单1. 简单的内存泄漏

 
  1. #include   
  2.  
  3. #include   
  4.  
  5. #include   
  6.  
  7. int LeakTest(char * Para)  
  8.  
  9. {  
  10.  
  11. if( NULL ==Para){  
  12.  
  13. //local_log("LeakTest Func: empty parameter\n");  
  14.  
  15. return -1;  
  16.  
  17. }  
  18.  
  19. char *  Logmsg  =  new  char[128];  
  20.  
  21. if( NULL  == Logmsg){  
  22.  
  23. //local_log("memeory allocation failed\n");  
  24.  
  25. return -2;  
  26.  
  27. }  
  28.  
  29. sprintf(Logmsg,"LeakTest routine exit: '%s'.\n", Para);  
  30.  
  31. //local_log(Logmsg);  
  32.  
  33. return 0;  
  34.  
  35. }  
  36.  
  37. int main(int argc,char **argv )  
  38.  
  39. {  
  40.  
  41. char szInit [] = "testcase1";  
  42.  
  43. LeakTest(szInit);  
  44.  
  45. return 0;  
  46.  
  47. }  
  48.  

(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 以上

2011年01月17日 星期一 08:54

清单2. 用作 Beam 分析的代码

 
  1. #include   
  2.  
  3. #include   
  4.  
  5. #include   
  6.  
  7. int *p;  
  8.  
  9. void  
  10.  
  11. foo(int a)  
  12.  
  13. {  
  14.  
  15. int b, c;  
  16.  
  17. b  =  0 ;  
  18.  
  19. if(!p)  
  20.  
  21. c  =  1 ;  
  22.  
  23. if(c  >  a)  
  24.  
  25. c += p[1];  
  26.  
  27. }  
  28.  
  29. int LeakTest(char * Para)  
  30.  
  31. {  
  32.  
  33. char *  Logmsg  =  new  char[128];  
  34.  
  35. if(( Para ==NULL)||( Logmsg  == NULL))  
  36.  
  37. return -1;  
  38.  
  39. sprintf(Logmsg,"LeakTest routine exit: '%s'.\n", Para);  
  40.  
  41. return 0;  
  42.  
  43. }  
  44.  
  45. int main(int argc,char **argv )  
  46.  
  47. {  
  48.  
  49. char szInit [] = "testcase1";  
  50.  
  51. LeakTest(szInit);  
  52.  
  53. return 0;  
  54.  
  55. }  
  56.  

2011年01月17日 星期一 08:54

下面以 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++ 编译器,按下面的命令进行编译 (前面两个命令是设置编译器环境变量):

 

 
  1. ./beam-3.4.2/bin/beam_configure --c gcc  
  2.  
  3. ./beam-3.4.2/bin/beam_configure --cpp g++  
  4.  
  5. ./beam-3.4.2/bin/beam_compile  --beam::compiler = compiler_cpp_config .tcl -cpp code2.cpp  
  6.  

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

 
  1. "code2.cpp", line 10: warning: variable "b" was set but never used  
  2.  
  3. int b, c;  
  4.  
  5. ^  
  6.  
  7. BEAM_VERSION = 3 .4.2  
  8.  
  9. BEAM_ROOT =/home/hanzb/memdetect  
  10.  
  11. BEAM_DIRECTORY_WRITE_INNOCENTS =  
  12.  
  13. BEAM_DIRECTORY_WRITE_ERRORS =  
  14.  
  15. -- ERROR23(heap_memory) /*memory leak*/  > > > ERROR23_LeakTest_7b00071dc5cbb458  
  16.  
  17. "code2.cpp", line 24: memory leak  
  18.  
  19. ONE POSSIBLE PATH LEADING TO THE ERROR:  
  20.  
  21. "code2.cpp", line 22: allocating using `operator new[]' (this memory will not be freed)  
  22.  
  23. "code2.cpp", line 22: assigning into `Logmsg'  
  24.  
  25. "code2.cpp", line 24: deallocating `Logmsg' because exiting its scope  
  26.  
  27. (losing last pointer to the memory)  
  28.  
  29. -- ERROR1 /*uninitialized*/  > > > ERROR1_foo_60c7889b2b608  
  30.  
  31. "code2.cpp", line 16: uninitialized `c'  
  32.  
  33. ONE POSSIBLE PATH LEADING TO THE ERROR:  
  34.  
  35. "code2.cpp", line 10: allocating `c'  
  36.  
  37. "code2.cpp", line 13: the if-condition is false  
  38.  
  39. "code2.cpp", line 16: getting the value of `c'  
  40.  
  41. VALUES AT THE END OF THE PATH:  
  42.  
  43. p != 0  
  44.  
  45. -- ERROR2 /*operating on NULL*/  > > > ERROR2_foo_af57809a2b615  
  46.  
  47. "code2.cpp", line 17: invalid operation involving NULL pointer  
  48.  
  49. ONE POSSIBLE PATH LEADING TO THE ERROR:  
  50.  
  51. "code2.cpp", line 13: the if-condition is true (used as evidence that error is possible)  
  52.  
  53. "code2.cpp", line 16: the if-condition is true  
  54.  
  55. "code2.cpp", line 17: invalid operation `[]' involving NULL pointer `p'  
  56.  
  57. VALUES AT THE END OF THE PATH:  
  58.  
  59. c  =  1  
  60.  
  61. p  =  0  
  62.  
  63. < = 0  
  64.  

 

如下红色区域有误,请重新填写。

    你的回复:

    请 登录 后回复。还没有在Zeuux哲思注册吗?现在 注册 !

    Zeuux © 2024

    京ICP备05028076号