查处内存泄漏的方法

2024-04-25 09:08
文章标签 方法 内存 泄漏 查处

本文主要是介绍查处内存泄漏的方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

由于 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值):

[c-sharp]  view plain copy
  1. ps -aux  

 

2. 静态分析

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

2.1 手动检测

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


清单1. 简单的内存泄漏

[c-sharp] view plaincopy
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <stdlib.h>  
  4. int LeakTest(char * Para)  
  5. {  
  6.         if(NULL==Para){  
  7.                 //local_log("LeakTest Func: empty parameter/n");  
  8.                 return -1;  
  9.         }  
  10.         char * Logmsg = new char[128];  
  11.         if(NULL == Logmsg){  
  12.                 //local_log("memeory allocation failed/n");  
  13.                 return -2;  
  14.         }  
  15.         sprintf(Logmsg,"LeakTest routine exit: '%s'./n", Para);  
  16.         //local_log(Logmsg);  
  17.         return 0;  
  18. }  
  19. int   main(int argc,char **argv )  
  20. {  
  21.         char szInit [] = "testcase1";  
  22.         LeakTest(szInit);  
  23.         return 0;  
  24. }  

 

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 分析的代码

[c-sharp] view plaincopy
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <stdlib.h>  
  4. int *p;  
  5. void  
  6. foo(int a)  
  7. {  
  8.   int b, c;  
  9.   b = 0;  
  10.   if(!p)   
  11.     c = 1;  
  12.   if(c > a)  
  13.     c += p[1];  
  14. }  
  15. int LeakTest(char * Para)  
  16. {  
  17.         char * Logmsg = new char[128];  
  18.         if((Para==NULL)||(Logmsg == NULL))  
  19.                 return -1;          
  20.         sprintf(Logmsg,"LeakTest routine exit: '%s'./n", Para);          
  21.         return 0;  
  22. }  
  23. int   main(int argc,char **argv )  
  24. {  
  25.         char szInit [] = "testcase1";  
  26.         LeakTest(szInit);  
  27.         return 0;  
  28. }  

 

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

[c-sharp]  view plain copy
  1. ./beam-3.4.2/bin/beam_configure  --c gcc  
  2. ./beam-3.4.2/bin/beam_configure  --cpp g++  
  3. ./beam-3.4.2/bin/beam_compile  --beam::compiler=compiler_cpp_config.tcl  -cpp code2.cpp  

 

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

[c-sharp]  view plain copy
  1. "code2.cpp", line 10: warning: variable "b" was set but never used  
  2.     int b, c;  
  3.         ^  
  4. BEAM_VERSION=3.4.2  
  5. BEAM_ROOT=/home/hanzb/memdetect  
  6. BEAM_DIRECTORY_WRITE_INNOCENTS=  
  7. BEAM_DIRECTORY_WRITE_ERRORS=  
  8. -- ERROR23(heap_memory)     /*memory leak*/     >>>ERROR23_LeakTest_7b00071dc5cbb458  
  9. "code2.cpp", line 24: memory leak  
  10. ONE POSSIBLE PATH LEADING TO THE ERROR:  
  11.  "code2.cpp", line 22: allocating using `operator new[]' (this memory will not be freed)  
  12.  "code2.cpp", line 22: assigning into `Logmsg'  
  13.  "code2.cpp", line 24: deallocating `Logmsg' because exiting its scope   
  14.                        (losing last pointer to the memory)  
  15. -- ERROR1     /*uninitialized*/     >>>ERROR1_foo_60c7889b2b608  
  16. "code2.cpp", line 16: uninitialized `c'  
  17. ONE POSSIBLE PATH LEADING TO THE ERROR:  
  18.  "code2.cpp", line 10: allocating `c'  
  19.  "code2.cpp", line 13: the if-condition is false  
  20.  "code2.cpp", line 16: getting the value of `c'  
  21.  VALUES AT THE END OF THE PATH:  
  22.   p != 0   
  23. -- ERROR2     /*operating on NULL*/     >>>ERROR2_foo_af57809a2b615  
  24. "code2.cpp", line 17: invalid operation involving NULL pointer  
  25. ONE POSSIBLE PATH LEADING TO THE ERROR:  
  26.  "code2.cpp", line 13: the if-condition is true (used as evidence that error is possible)  
  27.  "code2.cpp", line 16: the if-condition is true  
  28.  "code2.cpp", line 17: invalid operation `[]' involving NULL pointer `p'  
  29.  VALUES AT THE END OF THE PATH:  
  30.   c = 1   
  31.   p = 0   
  32.   a <= 0  

 

3. 动态运行检测

实时检测工具主要有 valgrind, Rational purify 等。

3.1 Valgrind

valgrind 是帮助程序员寻找程序里的 bug 和改进程序性能的工具。程序通过 valgrind 运行时,valgrind 收集各种有用的信息,通过这些信息可以找到程序中潜在的 bug 和性能瓶颈。

Valgrind 现在提供多个工具,其中最重要的是 Memcheck,Cachegrind,Massif 和 Callgrind。Valgrind 是在 Linux 系统下开发应用程序时用于调试内存问题的工具。它尤其擅长发现内存管理的问题,它可以检查程序运行时的内存泄漏问题。其中的 memecheck 工具可以用来寻找 c、c++ 程序中内存管理的错误。可以检查出下列几种内存操作上的错误:

  • 读写已经释放的内存
  • 读写内存块越界(从前或者从后)
  • 使用还未初始化的变量
  • 将无意义的参数传递给系统调用
  • 内存泄漏

3.2 Rational purify

Rational Purify 主要针对软件开发过程中难于发现的内存错误、运行时错误。在软件开发过程中自动地发现错误,准确地定位错误,提供完备的错误信息,从而减少了调试时间。同 时也是市场上唯一支持多种平台的类似工具,并且可以和很多主流开发工具集成。Purify 可以检查应用的每一个模块,甚至可以查出复杂的多线程 或进程应用中的错误。另外不仅可以检查 C/C++,还可以对 Java 或 .NET 中的内存泄漏问题给出报告。

在 Linux 系统中,使用 Purify 需要重新编译程序。通常的做法是修改 Makefile 中的编译器变量。下面是用来编译本文中程序的 Makefile:

[c-sharp]  view plain copy
  1. CC=purify gcc  

 

首先运行 Purify 安装目录下的 purifyplus_setup.sh 来设置环境变量,然后运行 make 重新编译程序。

[c-sharp]  view plain copy
  1. ./purifyplus_setup.sh  

 

下面给出编译一个代码文件的示例,源代码文件命名为 test3.cpp. 用 purify 和 g++ 的编译命令如下,‘-g’是编译时加上调试信息。

[c-sharp]  view plain copy
  1. purify g++ -g test3.cpp –o test  

 

运行编译生成的可执行文件 test,就可以得到图1,可以定位出内存泄漏的具体位置。

[c-sharp]  view plain copy
  1. ./test  

 

清单3. Purify 分析的代码

[c-sharp] view plaincopy
  1. #include <unistd.h>   
  2.  char * Logmsg;  
  3. int LeakTest(char * Para)  
  4. {  
  5.         if(NULL==Para){  
  6.                 //local_log("LeakTest Func: empty parameter/n");  
  7.                 return -1;  
  8.         }  
  9.         Logmsg = new char[128];  
  10.         for (int i = 0 ; i < 128; i++)  
  11.             Logmsg[i] = i%64;  
  12.         if(NULL == Logmsg){  
  13.                 //local_log("memeory allocation failed/n");  
  14.                 return -2;  
  15.         }  
  16.         sprintf(Logmsg,"LeakTest routine exit: '%s'./n", Para);  
  17.         //local_log(Logmsg);  
  18.         return 0;  
  19. }  
  20. int   main(int argc,char **argv )  
  21. {  
  22.         char szInit [] = "testcase1";  
  23.         int i;  
  24.          LeakTest(szInit);  
  25.         for (i=0; i < 2; i++){  
  26.             if(i%200 == 0)  
  27.                 LeakTest(szInit);  
  28.             sleep(1);  
  29.         }          
  30.         return 0;  
  31. }  

需要指出的是,程序必须编译成调试版本才可以定位到具体哪行代码发生了内存泄漏。即在 gcc 或者 g++ 中,必须使用 "-g" 选项。


图 1 purify 的输出结果

 

结论

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

 

原文:http://www.ibm.com/developerworks/cn/linux/l-cn-memleak/index.html

 

==================================================================================

以上是Linux平台,Windows平台如下

==================================================================================

Visual Leak Detector 是一款用于 Visual C++ 的免费的内存泄露检测工具。可以在http://www.codeproject.com/tools/visualleakdetector.asp 下载到。相比较其它的内存泄露检测工具,它在检测到内存泄漏的同时,还具有如下特点:

1、   可以得到内存泄漏点的调用堆栈,如果可以的话,还可以得到其所在文件及行号;

2、   可以得到泄露内存的完整数据;

3、   可以设置内存泄露报告的级别;

4、   它是一个已经打包的 lib ,使用时无须编译它的源代码。而对于使用者自己的代码,也只需要做很小的改动;

5、   他的源代码使用 GNU 许可发布,并有详尽的文档及注释。对于想深入了解堆内存管理的读者,是一个不错的选择。

      

       可见,从使用角度来讲, Visual Leak Detector 简单易用,对于使用者自己的代码,唯一的修改是 #include Visual Leak Detector 的头文件后正常运行自己的程序,就可以发现内存问题。从研究的角度来讲,如果深入 Visual Leak Detector 源代码,可以学习到堆内存分配与释放的原理、内存泄漏检测的原理及内存操作的常用技巧等。

       本文首先将介绍 Visual Leak Detector 的使用方法与步骤,然后再和读者一起初步的研究 Visual Leak Detector 的源代码,去了解 Visual Leak Detector 的工作原理。

 

使用 Visual Leak Detector(1.0)

       下面让我们来介绍如何使用这个小巧的工具。

       首先从网站上下载 zip 包,解压之后得到 vld.h, vldapi.h, vld.lib, vldmt.lib, vldmtdll.lib, dbghelp.dll 等文件。将 .h 文件拷贝到 Visual C++ 的默认 include 目录下,将 .lib 文件拷贝到 Visual C++ 的默认 lib 目录下,便安装完成了。因为版本问题,如果使用 windows 2000 或者以前的版本,需要将 dbghelp.dll 拷贝到你的程序的运行目录下,或其他可以引用到的目录。

注:我下载的是较新版1.9,直接安装到系统中。因此使用时必须先在VC中设置一下目录。 

       接下来需要将其加入到自己的代码中。方法很简单,只要在包含入口函数的 .cpp 文件中包含 vld.h 就可以。如果这个 cpp文件包含了 stdafx.h ,则将包含 vld.h 的语句放在 stdafx.h 的包含语句之后,否则放在最前面。如下是一个示例程序:

#include <vld.h>

void main()

{

…                

}

       接下来让我们来演示如何使用 Visual Leak Detector 检测内存泄漏。下面是一个简单的程序,用 new 分配了一个 int 大小的堆内存,并没有释放。其申请的内存地址用 printf 输出到屏幕上。

 

 

编译运行后,在标准输出窗口得到:

p=003a89c0

 

在 Visual C++ 的 Output 窗口得到:

 

WARNING: Visual Leak Detector detected memory leaks!

---------- Block 57 at 0x003A89C0: 4 bytes ----------  --57 号块0x003A89C0 地址泄漏了4 个字节

  Call Stack:                                                 -- 下面是调用堆栈

    d:/test/testvldconsole/testvldconsole/main.cpp (7): f  -- 表示在main.cpp 第7 行的f() 函数

    d:/test/testvldconsole/testvldconsole/main.cpp (14): main  双击以引导至对应代码处

    f:/rtm/vctools/crt_bld/self_x86/crt/src/crtexe.c (586): __tmainCRTStartup

    f:/rtm/vctools/crt_bld/self_x86/crt/src/crtexe.c (403): mainCRTStartup

    0x7C816D4F (File and line number not available): RegisterWaitForInputIdle

  Data:                                   -- 这是泄漏内存的内容,0x12345678

    78 56 34 12                                                  xV4..... ........

 

Visual Leak Detector detected 1 memory leak.    

第二行表示 57 号块有 4 字节的内存泄漏,地址为 0x003A89C0 ,根据程序控制台的输出,可以知道,该地址为指针 p。程序的第 7 行, f() 函数里,在该地址处分配了 4 字节的堆内存空间,并赋值为 0x12345678 ,这样在报告中,我们看到了这 4 字节同样的内容。

可以看出,对于每一个内存泄漏,这个报告列出了它的泄漏点、长度、分配该内存时的调用堆栈、和泄露内存的内容(分别以 16 进制和文本格式列出)。双击该堆栈报告的某一行,会自动在代码编辑器中跳到其所指文件的对应行。这些信息对于我们查找内存泄露将有很大的帮助。

这是一个很方便易用的工具,安装后每次使用时,仅仅需要将它头文件包含进来重新 build 就可以。而且,该工具仅在build Debug 版的时候会连接到你的程序中,如果 build Release 版,该工具不会对你的程序产生任何性能等方面影响。所以尽可以将其头文件一直包含在你的源代码中。

 

在使用上, Visual Leak Detector 简单方便,结果报告一目了然。在原理上, Visual Leak Detector 针 对内存泄漏问题的特点,可谓对症下药——内存泄漏不是不容易发现吗?那就每次内存分配是都给记录下来,程序退出时算总账;内存泄漏现象出现时不是已时过境 迁,并非当时泄漏点的现场了吗?那就把现场也记录下来,清清楚楚的告诉使用者那块泄漏的内存就是在如何一个调用过程中泄漏掉的。

       Visual Leak Detector 是一个简单易用内存泄漏检测工具。现在最新的版本是 1.9a ,采用了新的检测机制,并在功能上有了很多改进。读者不妨体验一下。

原文:http://blog.csdn.net/bennyfun79/archive/2008/11/19/3336332.aspx

这篇关于查处内存泄漏的方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/934248

相关文章

macOS无效Launchpad图标轻松删除的4 种实用方法

《macOS无效Launchpad图标轻松删除的4种实用方法》mac中不在appstore上下载的应用经常在删除后它的图标还残留在launchpad中,并且长按图标也不会出现删除符号,下面解决这个问... 在 MACOS 上,Launchpad(也就是「启动台」)是一个便捷的 App 启动工具。但有时候,应

SpringBoot日志配置SLF4J和Logback的方法实现

《SpringBoot日志配置SLF4J和Logback的方法实现》日志记录是不可或缺的一部分,本文主要介绍了SpringBoot日志配置SLF4J和Logback的方法实现,文中通过示例代码介绍的非... 目录一、前言二、案例一:初识日志三、案例二:使用Lombok输出日志四、案例三:配置Logback一

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

mysql出现ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)的解决方法

《mysql出现ERROR2003(HY000):Can‘tconnecttoMySQLserveron‘localhost‘(10061)的解决方法》本文主要介绍了mysql出现... 目录前言:第一步:第二步:第三步:总结:前言:当你想通过命令窗口想打开mysql时候发现提http://www.cpp

Mysql删除几亿条数据表中的部分数据的方法实现

《Mysql删除几亿条数据表中的部分数据的方法实现》在MySQL中删除一个大表中的数据时,需要特别注意操作的性能和对系统的影响,本文主要介绍了Mysql删除几亿条数据表中的部分数据的方法实现,具有一定... 目录1、需求2、方案1. 使用 DELETE 语句分批删除2. 使用 INPLACE ALTER T

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE

CentOS 7部署主域名服务器 DNS的方法

《CentOS7部署主域名服务器DNS的方法》文章详细介绍了在CentOS7上部署主域名服务器DNS的步骤,包括安装BIND服务、配置DNS服务、添加域名区域、创建区域文件、配置反向解析、检查配置... 目录1. 安装 BIND 服务和工具2.  配置 BIND 服务3 . 添加你的域名区域配置4.创建区域

mss32.dll文件丢失怎么办? 电脑提示mss32.dll丢失的多种修复方法

《mss32.dll文件丢失怎么办?电脑提示mss32.dll丢失的多种修复方法》最近,很多电脑用户可能遇到了mss32.dll文件丢失的问题,导致一些应用程序无法正常启动,那么,如何修复这个问题呢... 在电脑常年累月的使用过程中,偶尔会遇到一些问题令人头疼。像是某个程序尝试运行时,系统突然弹出一个错误提

电脑提示找不到openal32.dll文件怎么办? openal32.dll丢失完美修复方法

《电脑提示找不到openal32.dll文件怎么办?openal32.dll丢失完美修复方法》openal32.dll是一种重要的系统文件,当它丢失时,会给我们的电脑带来很大的困扰,很多人都曾经遇到... 在使用电脑过程中,我们常常会遇到一些.dll文件丢失的问题,而openal32.dll的丢失是其中比较