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. 内存申请释放流程:
图 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 的检测结果相同。
- UNIX(如AIX)下内存泄露问题分析方法
- 内存泄露问题分析方法
- 内存泄露问题分析
- 内存泄露问题分析
- 如何用windbg分析内存泄露
- Android下分析内存泄露
- Android下分析内存泄露
- AIX 6.1环境IBM Java1.6内存泄露分析
- Java内存泄露问题分析
- Java内存泄露问题分析
- Java内存泄露问题分析
- Java内存泄露问题分析
- Java内存泄露问题分析
- Java内存泄露问题分析
- Android内存泄露问题分析
- Celery内存泄露问题分析
- 内存泄露分析方法(android内存溢出)
- ARC下内存泄露问题
- Eclipse Galileo 简介
- 编译工程(eng)版镜像(image)
- [linux]platform总线机制与wtd驱动开发
- hdu 1059 Dividing
- 输出三角形
- UNIX(如AIX)下内存泄露问题分析方法
- MFC中MoveToEx函数和LineTo函数
- C语言与JAVA区别
- C++与C指针操作种类
- 跟学韩老师学习java-servlet篇之三printwriter与outputStream区别、sendRedirect传参数、中文乱码
- Android学习 —— 数据的存储与访问方式三: SQLite数据库
- HOJ 1599-------Box of Bricks
- [linux]I/O端口分配request_region()
- 关于ListView顶部和底部滚(拖)动出现阴影解决方案