内存泄露简单的说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。
封装 new 和 delete 对内存泄漏进行分析通过对 new 和 delete 的封装,将 new 和 delete 的过程通过日志文件的保存记录下来。然后对日志文件进行分析,是否 new 和 delete 是匹配的,有哪些内存申请,但是没有释放。
下面通过一个简单的测试程序(此代码使用 C++ 语言实现,目前没有考虑申请数组的情况)进行演示:
这个测试程序申请了 pTemp1,pTemp2,pTemp3 的三块内存,但是仅仅释放了 pTemp3,存在 pTemp1 和 pTemp2 的内存泄露。
程序解释:
在每次内存申请时,将内存申请的信息注册到 MAP 表中,在每次内存释放时,将对应的内存信息从注册表中删除,这样注册表中将保存未释放的内存信息,按照一定的规则将注册表中的信息输出(定时或者进程退出等)。然后我们从输出信息中便可以分析出内存泄漏点。
通过自定义宏 DEMONEW 和 DEMODELETE 申请内存和释放内存,在这两个宏中,我们将内存的申请和释放做了记录,从而可以得到未释放内存的信息,请参考下面的程序实现流程图:
图 1. 内存申请释放流程:
图 2.DEMONEW 实现流程:
图 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 平台)工具对内存泄漏进行分析- 使用 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
输出分析:
从 purify 的输出可以看出,此测试程序存在两处内存泄漏,它分别是 func2 和 func3,在 tst.cpp 文件的第 9 和第 14 行。
- 使用 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 的检测结果相同。