您的位置:首页 > 编程语言 > C语言/C++

C++内存泄露问题定位经验案例

2016-04-27 18:14 573 查看
百度百科:
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。内存泄漏形象的比喻是“操作系统可提供给所有进程的存储空间正在被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃。所以“内存泄漏”是从操作系统的角度来看的。这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。由程序申请的一块内存,如果没有任何一个指针指向它,那么这块内存就泄漏了。上述描述中,最后一句红色字体部分存在问题。指针指向的内存,如果没有管理,也会产生内存泄露。在程序开发过程中,指针所指向的内存没有正确管理是产生内存问题的最主要原因。文中部分内容来自网络,如有不妥,请指正。1.详细描述内存增长过程如下:








1.1 检测方法
1.1.1. 检查容器
在实际项目中,程序运行过程内存消耗不断增加,并不一定是内存泄露造成的。如果代码量并不是特别大,应该先通读程序,查找代码逻辑上是否造成内存问题,特别是STL容器是否clear。在多线程中,部分容器经常作为全局变量使用,建议仔细检查相关容器是否需要clear。可以使用.size()函数来检查容器大小,打印或写入日志,查看大小是否持续增加。如:map<int, int> Intmap;std::cout << Intmap.size();1.1.2. 检查new、malloc
最好先不要直接使用内存工具进行对位,而是按模块阅读代码,检查所有new、malloc所分配出来的内存,是否都正确释放。如:(1) int* p = new int; delete p; p = NULL;(2) int* p = new int[10]; delete[] p; p = NULL; malloc同理,不清楚的请寻找相关资料。 按模块检查代码的好处是能够让你自己更理解代码,特别是你维护的代码经常是由别人开发实现的。1.1.3. 使用库
Windows下:Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法,原理大致如下:内存分配要通过CRT在运行时实现,只要在分配内存和释放内存时分别做好记录,程序结束时对比分配内存和释放内存的记录就可以确定是不是有内存泄漏。在vs中启用内存检测的方法如下:STEP1,在程序中包括以下语句:(#include 语句必须采用下文所示顺序。如果更改了顺序,所使用的函数可能无法正常工作。)
123#define _CRTDBG_MAP_ALLOC#include <stdlib.h>#include <crtdbg.h>
通过包括 crtdbg.h,将 mallocfree 函数映射到它们的调试版本,即 _malloc_dbg_free_dbg,这两个函数将跟踪内存分配和释放。此映射只在调试版本(在其中定义了_DEBUG)中发生。发布版本使用普通的 malloc 和 free 函数。#define 语句将 CRT 堆函数的基版本映射到对应的“Debug”版本。并非绝对需要该语句;但如果没有该语句,内存泄漏转储包含的有用信息将较少。STEP2,在添加了上述语句之后,可以通过在程序中包括以下语句(通常应恰好放在程序退出位置之前)来转储内存泄漏信息:
1_CrtDumpMemoryLeaks();
此时,完整的代码如下:
1234567891011121314151617181920#define _CRTDBG_MAP_ALLOC#include <stdlib.h>#include <crtdbg.h> #include <iostream>using namespace std; void GetMemory(char *p, int num){ p = (char*)malloc(sizeof(char) * num);} int main(int argc,char** argv){ char *str = NULL; GetMemory(str, 100); cout<<"Memory leak test!"<<endl; _CrtDumpMemoryLeaks(); return 0;}
当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在“输出”窗口中显示内存泄漏信息。内存泄漏信息如下所示:

如果没有使用 #define _CRTDBG_MAP_ALLOC 语句,内存泄漏转储将如下所示:

未定义 _CRTDBG_MAP_ALLOC 时,所显示的会是:内存分配编号(在大括号内)。块类型(普通、客户端或 CRT)。“普通块”是由程序分配的普通内存。“客户端块”是由 MFC 程序用于需要析构函数的对象的特殊类型内存块。 MFC new 操作根据正在创建的对象的需要创建普通块或客户端块。“CRT 块”是由 CRT 库为自己使用而分配的内存块。 CRT 库处理这些块的释放,因此不大可能在内存泄漏报告中看到这些块,除非出现严重错误(例如 CRT 库损坏)。从不会在内存泄漏信息中看到下面两种块类型:“可用块”是已释放的内存块。“忽略块”是您已特别标记的块,因而不出现在内存泄漏报告中。十六进制形式的内存位置。以字节为单位的块大小。前 16 字节的内容(亦为十六进制)。定义了 _CRTDBG_MAP_ALLOC 时,还会显示在其中分配泄漏的内存的文件。文件名后括号中的数字(本示例中为 10)是该文件中的行号。注意:如果程序总是在同一位置退出,调用 _CrtDumpMemoryLeaks 将非常容易。如果程序从多个位置退出,则无需在每个可能退出的位置放置对_CrtDumpMemoryLeaks 的调用,而可以在程序开始处包含以下调用:
1_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
该语句在程序退出时自动调用 _CrtDumpMemoryLeaks。必须同时设置 _CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF 两个位域,如前面所示。定位具体的内存泄漏地方:通过上面的方法,我们几乎可以定位到是哪个地方调用内存分配函数malloc和new等,如上例中的GetMemory函数中,即第10行!但是不能定位到,在哪个地方调用GetMemory()导致的内存泄漏,而且在大型项目中可能有很多处调用GetMemory。如何要定位到在哪个地方调用GetMemory导致的内存泄漏?定位内存泄漏的另一种技术涉及在关键点对应用程序的内存状态拍快照。 CRT 库提供一种结构类型 _CrtMemState,您可用它存储内存状态的快照:
1_CrtMemState s1, s2, s3;
若要在给定点对内存状态拍快照,请向 _CrtMemCheckpoint 函数传递 _CrtMemState 结构。该函数用当前内存状态的快照填充此结构:
1_CrtMemCheckpoint( &s1 );
通过向 _CrtMemDumpStatistics 函数传递 _CrtMemState 结构,可以在任意点转储该结构的内容:
1_CrtMemDumpStatistics( &s1 );
若要确定代码中某一部分是否发生了内存泄漏,可以在该部分之前和之后对内存状态拍快照,然后使用 _CrtMemDifference 比较这两个状态:
12345_CrtMemCheckpoint( &s1 );// memory allocations take place here_CrtMemCheckpoint( &s2 );if ( _CrtMemDifference( &s3, &s1, &s2) ) _CrtMemDumpStatistics( &s3 );
顾名思义,_CrtMemDifference 比较两个内存状态(s1 和 s2),生成这两个状态之间差异的结果(s3)。在程序的开始和结尾放置 _CrtMemCheckpoint 调用,并使用_CrtMemDifference 比较结果,是检查内存泄漏的另一种方法。如果检测到泄漏,则可以使用 _CrtMemCheckpoint 调用通过二进制搜索技术来划分程序和定位泄漏。如上面的例子程序我们可以这样来定位确切的调用GetMemory的地方:
12345678910111213141516171819202122#define _CRTDBG_MAP_ALLOC#include <stdlib.h>#include <crtdbg.h>#include <iostream>using namespace std;_CrtMemState s1, s2, s3;void GetMemory(char *p, int num){ p = (char*)malloc(sizeof(char) * num);}int main(int argc,char** argv){ _CrtMemCheckpoint( &s1 ); char *str = NULL; GetMemory(str, 100); _CrtMemCheckpoint( &s2 ); if ( _CrtMemDifference( &s3, &s1, &s2) ) _CrtMemDumpStatistics( &s3 ); cout<<"Memory leak test!"<<endl; _CrtDumpMemoryLeaks(); return 0;}
调试时,程序输出如下结果:

这说明在s1和s2之间存在内存泄漏!!!如果GetMemory不是在s1和s2之间调用,那么就不会有信息输出。Linux下内存检查:在上面我们介绍了,vs中在代码中“包含crtdbg.h,将 mallocfree 函数映射到它们的调试版本,即 _malloc_dbg_free_dbg,这两个函数将跟踪内存分配和释放。此映射只在调试版本(在其中定义了_DEBUG)中发生。发布版本使用普通的 mallocfree 函数。”即为malloc和free做了钩子,用于记录内存分配信息。#include <stdlib.h>
#include <mcheck.h>

int main(void) {

mtrace(); /* Starts the recording of memory allocations and releases */

int* a = NULL;

a = malloc(sizeof(int)); /* allocate memory and assign it to the pointer */

if (a == NULL) {
return 1; /* error */
}

free(a); /* we free the memory we allocated so we don't have leaks */

muntrace();

return 0; /* exit */
}
详细内容请参考http://en.wikipedia.org/wiki/Mtrace1.1.4. 使用工具
Windows下:Visual Leak Detector VLD网址:http://vld.codeplex.com/ http://www.codeproject.com/Articles/9815/Visual-Leak-Detector-Enhanced-Memory-Leak-Detectio下载Visual Leak Detector,打开Visual C++ IDE的"工具"→"选项"→"项目和解决方案"→"VC++ 目录",在"包含文件"中增加VLD的头文件路径"\include"路径,在"库文件"增加VLD库文件的"\lib\Win32"路径,此外动态库的"\bin\Win32"路径在安装时已经添加到环境变量里面了,若是未添加,则需要手动拷贝"\bin\Win32"下的文件到可执行文件所在的目录中(拷贝的文件有dbghelp.dll/Microsoft.DTfW.DHL.manifest/vld_x86.dll/vld.ini)。接下来需要将VLD加入到自己的代码中。方法很简单,只要在包含入口函数的.cpp文件中包含vld.h就可以。如果这个cpp文件中包含了stdafx.h,则将包含vld.h的语句放在stdafx.h的包含语句之后,否则放在最前面。 示例程序:#include<vld.h> // 包含VLD的头文件#include<stdlib.h>#include<stdio.h>void f(){ int *p = new int(0x12345678); printf("p=%08x, ", p);}int main(){ f(); return 0;} 注:VLD只能在Windows下使用,在包含vld.h头文件时增加预编译选项。 注:在Release模式下,不会链接VisualLeak Detector。 注:Visual LeakDetector有一些配置项,可以设置内存泄露报告的保存地(文件、调试器),拷贝"\Visual Leak Detector"路径下的vld.ini文件到执行文件所在的目录下(在IDE运行的话,则需要拷贝到工程目录下),修改以下项:ReportFile =.\memory_leak_report.txt ReportTo = bothLinux下:valgrind命令:yum install valgrind进行安装。/usr/include/gnu/stubs.h:7:27: error: gnu/stubs-32.h: No such file or directorymake: *** [TrafficInfo.o] Error 1是缺少需要的程序包解决办法:安装缺少的包: yum install glibc-devel命令:valgrind --tool=memcheck ./TestPRTI即可对TestPRTI程序进行内存检测。



如上图所示知道:
==6118== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==6118== at 0x4024F20: malloc (vg_replace_malloc.c:236)
==6118== by 0x8048724: GetMemory(char*, int) (in /home/netsky/workspace/a.out)
==6118== by 0x804874E: main (in /home/netsky/workspace/a.out)
是在main中调用了GetMemory导致的内存泄漏,GetMemory中是调用了malloc导致泄漏了100字节的内存。详细内容请参考/article/1658758.htmlhttp://www.linuxidc.com/Linux/2014-10/108087.htm
1.2 智能指针
智能指针便可以有效缓解这类问题,但并不意味着使用智能指针就不再会产生内存泄露问题。包括:std::auto_ptrboost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。所有智能指针都重载了“operator->”操作符,直接返回对象的引用,用以操作对象。访问智能指针原来的方法则使用“.”操作符。具体使用方法详见http://blog.csdn.net/xt_xiaotian/article/details/5714477
本文出自 “爱幻想的菜鸟” 博客,请务必保留此出处http://3dot1415926.blog.51cto.com/8887443/1768325
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: