UNIX(如AIX)下内存泄露问题分析方法

来源:互联网 发布:侠客风云传32位优化 编辑:程序博客网 时间:2024/05/08 00:02

内存泄露简单的说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。

封装 new 和 delete 对内存泄漏进行分析

通过对 new 和 delete 的封装,将 new 和 delete 的过程通过日志文件的保存记录下来。然后对日志文件进行分析,是否 new 和 delete 是匹配的,有哪些内存申请,但是没有释放。

下面通过一个简单的测试程序(此代码使用 C++ 语言实现,目前没有考虑申请数组的情况)进行演示:

这个测试程序申请了 pTemp1,pTemp2,pTemp3 的三块内存,但是仅仅释放了 pTemp3,存在 pTemp1 和 pTemp2 的内存泄露。

程序解释:

在每次内存申请时,将内存申请的信息注册到 MAP 表中,在每次内存释放时,将对应的内存信息从注册表中删除,这样注册表中将保存未释放的内存信息,按照一定的规则将注册表中的信息输出(定时或者进程退出等)。然后我们从输出信息中便可以分析出内存泄漏点。

通过自定义宏 DEMONEW 和 DEMODELETE 申请内存和释放内存,在这两个宏中,我们将内存的申请和释放做了记录,从而可以得到未释放内存的信息,请参考下面的程序实现流程图:


图 1. 内存申请释放流程:
图 1. 内存申请释放流程: 

图 2.DEMONEW 实现流程:
图 2.DEMONEW 实现流程: 

图 3.DEMODELETE 实现流程:
图 3.DEMODELETE 实现流程: 

测试程序代码:

 #include <map>  #include <iostream>  #include <string>  #include <fstream>  // 申请内存时,存储 new 位置的数据结构 typedef struct {  std::string filename;  int line;  } MEMINFO;  // 输出文件 std::ofstream loginfo("//tmp/memory.log");  typedef std::map<long long, MEMINFO> MemMap;  // 存储内存申请记录(在每次内存申请时,将内存申请的地址作为键值, // 内存申请操作所在的文件名和行号作为内容,存储到下面的数据结构 memmap 中) MemMap memmap;  // 注册内存申请信息到上面的 map 容器中,输入的参数分别为内存地址,文件名,行号 void RegMemInfo(long long addr, const char *fname, long long lnum)  {         MEMINFO info;         if (fname)         {             info.filename = fname;         }         info.line = lnum;         memmap.insert(MemMap::value_type(addr, info));  };  // 卸载内存申请信息从上面的 map 容器中,输入的参数为内存地址 void UnRegMemInfo(long long addr)  {         if (memmap.end() != memmap.find(addr))         {                 memmap.erase(addr);         }  }  // 定义宏 DEMONEW,封装了内存申请的操作,在内存申请成功后,调用 RegMemInfo 功能, // 将内存信息注册到 map 容器中 #define DEMONEW(p, ptype)\  do \  {\         p = new ptype;\         if (p)\         {\             RegMemInfo((long long)p, __FILE__, __LINE__);\         }\         else\     {\             std::cout<<"NEW failed"<<std::endl;\     }\  }\  while(0)  // 定义宏 DEMODELETE,封装了内存释放的操作,在内存释放时,调用 UnRegMemInfo  // 功能,将内存信息从 map 容器中删除 #define DEMODELETE(p) \  do\  {\         if (p)\         {\                 UnRegMemInfo((long long)p);\                 delete p;\                 p = 0;\         }\  }while(0)  // 写信息流内容到文件 void WriteString(std::string buf)  {         loginfo << buf <<std::endl;  }  // 将整数转换为字符串 std::string Int2Str(int value)  {         char buf[16] = {0};         sprintf(buf, "%d", value);         return buf;  }  // 输出 map 容器中存储的内存没有释放的信息 void Output()  {         loginfo.clear();         if (memmap.empty())         {             WriteString("No Memory leak.");             return;         }         MemMap::iterator iter;         WriteString("The Memory leak is below:");         for (iter = memmap.begin(); iter != memmap.end(); ++iter)         {                 std::string buf;                 std::string sAddr = Int2Str(iter->first);                 std::string sLine = Int2Str(iter->second.line);                 buf += "memory Address ";                 buf += sAddr;                 buf += ": FILE ";                 buf += iter->second.filename;                 buf += ", LINE ";                 buf += sLine;                 buf += " no freed";                 WriteString(buf);         }  }  // 测试程序主入口函数 int main(int argc,  char* argv[])  {         char* pTemp1 = 0;         DEMONEW(pTemp1, char);         char* pTemp2 = 0;         DEMONEW(pTemp2, char);         char* pTemp3 = 0;         DEMONEW(pTemp3, char);         DEMODELETE(pTemp1);         Output();         loginfo.close();         return 0;  }                                

上面测试程序的输出是:

 [dyu@xilinuxbldsrv ~]$ vi /tmp/memory.log  The Memory leak is below:  memory Address 280929008: FILE test.cpp, LINE 109 no freed  memory Address 280929152: FILE test.cpp, LINE 111 no freed 

输出分析:

从输出结果我们可以发现,此测试程序在 test.cpp 文件的 109 和 111 行各有一处内存泄漏,查看源代码,它们分别是 pTemp1 和 pTemp2。

使用 Purify(适用所有 UNIX 平台)或者 valgrind(适用 Linux 平台)工具对内存泄漏进行分析
  1. 使用 Purify 对内存泄漏进行分析

    Purify 是 IBM Rational PurifyPlus 的工具之一, 是一个面向 VC、VB 或者 Java 开发的测试 Visual C/C++ 和 Java 代码中与内存有关的错误的工具,它确保整个应用程序的质量和可靠性。在查找典型的 C/C++ 程序中的传统内存访问错误, Rational Purify 可以大显身手。在 UNIX 系统中,使用 Purify 需要重新编译程序。通常的做法是修改 Makefile 中的编译器变量。

    例如定义 CC 变量为 purify gcc

     CC=purify gcc 

    首先运行 Purify 安装目录下的 purifyplus_setup.sh 来设置环境变量,然后运行 make 重新编译程序。需要指出的是,程序必须编译成调试版本。在编译器命令(例如 Solaris 的 CC 编译器,Linux 的 gcc 编译器等)后,也就是必须使用"-g"选项。在重新编译的程序运行结束后,Purify 会打印出一个分析报告。

    测试程序(此代码使用 C++ 语言实现):

     #include <stdlib.h>  void func1()  {     //char* pBuf = new char;  }  void func2()  {     char* pBuf = new char;  }  void func3()  {     char* pBuf = new char;  }  int main()  {     func1();     func2();     func3();     return 0;  } 

    编译程序:

     [dyu@xilinuxbldsrv purify]$ purify g++ -g tst.cpp -o tst1 

    Purify 输出:

     [dyu@xilinuxbldsrv purify]$ ./tst1  16:50:59 (rational) OUT: "PurifyPlusUNIX" dyu@xilinuxbldsrv   ****  Purify instrumented ./tst1 (pid 530 at Fri Apr  6 16:50:59 2012)   * Purify 7.0.0.0-014 090319 Linux (64-bit) (C) Copyright IBM Corporation. 1992,   * 2009 All Rights Reserved.    * For contact information type: "purify -help"  * For Purify Viewer output, set the DISPLAY environment variable.   * License successfully checked out.   * Command-line: ./tst1   * Options settings: -g++=yes -purify \ -purify-home=/home/dyu/purify/PurifyPlus.7.0.0.0-014/Rational/releases/\purify.i386_linux2.7.0.0.0-014-process-large-objects=yes -gcc3_path=/usr/bin/g++ \ -cache-dir=/home/dyu/purify/PurifyPlus.7.0.0.0-014/Rational/releases/\purify.i386_linux2.7.0.0.0-014\/cache ****  Purify instrumented ./tst1 (pid 530)  ****  Current file descriptors in use: 5  FIU: file descriptor 0: <stdin>  FIU: file descriptor 1: <stdout>  FIU: file descriptor 2: <stderr>  FIU: file descriptor 26: <reserved for Purify internal use>  FIU: file descriptor 27: <reserved for Purify internal use>  ****  Purify instrumented ./tst1 (pid 530)  ****  Purify: Searching for all memory leaks...  Memory leaked: 2 bytes (100%); potentially leaked: 0 bytes (0%)  MLK: 1 byte leaked at 0xa457098   * This memory was allocated from:         malloc         [rtlib.o]         operator new(unsigned long) [libstdc++.so.6]         operator new(unsigned long) [rtlib.o]         func2()        [tst.cpp:9]         main           [tst.cpp:20]         __libc_start_main [libc.so.6]         _start         [crt1.o]  MLK: 1 byte leaked at 0xa457138   * This memory was allocated from:         malloc         [rtlib.o]         operator new(unsigned long) [libstdc++.so.6]         operator new(unsigned long) [rtlib.o]         func3()        [tst.cpp:14]         main           [tst.cpp:21]         __libc_start_main [libc.so.6]         _start         [crt1.o]  Purify Heap Analysis (combining suppressed and unsuppressed blocks)                          Blocks        Bytes               Leaked          2            2   Potentially Leaked          0            0               In-Use          0            0   ----------------------------------------      Total Allocated          2            2 

    Purify 图形输出:

    安装 Xmanager 等工具,设置 DISPLAY 为本机 IP,见下图:

     [dyu@xilinuxbldsrv purify]$ export DISPLAY=9.119.131.33:0 



    Figure xxx. Requires a heading 

    输出分析:

    从 purify 的输出可以看出,此测试程序存在两处内存泄漏,它分别是 func2 和 func3,在 tst.cpp 文件的第 9 和第 14 行。

  2. 使用 valgrind(现在仅仅支持 Linux 平台)对内存泄漏进行分析

    Valgrind 是一套 Linux 下,开放源代码(GPL V2)的仿真调试工具的集合。Valgrind 由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架,它模拟了一个 CPU 环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。Valgrind 在对程序进行侦测的时候,不需要对程序进行重新编译。

    下面使用 valgrind 对一个简单的测试程序进行。

    测试程序:

    同 Purify 的测试程序相同。

    编译程序:

     [dyu@xilinuxbldsrv purify]$ g++ -g tst.cpp -o tst 

    valgrind 输出:

     [dyu@xilinuxbldsrv purify]$ valgrind --leak-check=full ./tst  ==25396== Memcheck, a memory error detector  ==25396== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.  ==25396== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info  ==25396== Command: ./tst  ==25396==  ==25396==  ==25396== HEAP SUMMARY:  ==25396==     in use at exit: 2 bytes in 2 blocks  ==25396==   total heap usage: 2 allocs, 0 frees, 2 bytes allocated  ==25396==  ==25396== 1 bytes in 1 blocks are definitely lost in loss record 1 of 2  ==25396==    at 0x4A0666E: operator new(unsigned long) (vg_replace_malloc.c:220)  ==25396==    by 0x4005C7: func2() (tst.cpp:9)  ==25396==    by 0x4005DB: main (tst.cpp:20)  ==25396==  ==25396== 1 bytes in 1 blocks are definitely lost in loss record 2 of 2  ==25396==    at 0x4A0666E: operator new(unsigned long) (vg_replace_malloc.c:220)  ==25396==    by 0x4005AF: func3() (tst.cpp:14)  ==25396==    by 0x4005E0: main (tst.cpp:21)  ==25396==  ==25396== LEAK SUMMARY:  ==25396==    definitely lost: 2 bytes in 2 blocks  ==25396==    indirectly lost: 0 bytes in 0 blocks  ==25396==      possibly lost: 0 bytes in 0 blocks  ==25396==    still reachable: 0 bytes in 0 blocks  ==25396==         suppressed: 0 bytes in 0 blocks  ==25396==  ==25396== For counts of detected and suppressed errors, rerun with: -v  ==25396== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 4 from 4)  [dyu@xilinuxbldsrv purify]$ 

    输出分析:

    从 valgrind 的输出可以看出,此测试程序存在两处内存泄漏,它分别是 func2 和 func3,在 tst.cpp 文件的第 9 和第 14 行,与 purify 的检测结果相同。

原创粉丝点击