现在的位置: 首页 > 综合 > 正文

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

2017年11月24日 ⁄ 综合 ⁄ 共 6710字 ⁄ 字号 评论关闭

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

封装 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 的检测结果相同。

抱歉!评论已关闭.