c++内存泄漏的原因及解决办法,C++ 内存泄漏
内存泄漏和检测-吴沁-博客花园
“该死的系统有内存泄漏问题”。由于项目中的各种因素,总有人抱怨内存泄露。系统长时间运行后,可用内存越来越少,甚至部分服务失败。内存泄漏是最难发现的常见错误之一,因为它不会导致任何问题,除非内存不足或调用malloc失败。事实上,在使用C/C等没有垃圾回收机制的语言时,你要花大量的时间处理如何正确释放内存的问题。如果程序运行的时间足够长,比如后台进程运行在服务器上,只要服务器保持运行不停机,那么一个小小的错误也会对程序产生重大影响,比如导致一些关键服务失败。
内存泄露我深有体会!实习的时候,公司的一个项目出现了内存泄露。项目的代码二非常大,后台进程很多,很难找到导致内存泄漏的地方。这个机会是我对如何发现内存泄漏问题有了一些经验,后来也做过相关实验。在这里,我分享一下如何调试和查找内存泄漏。主要内容如下:
1.内存泄漏介绍
2.Windows平台下的内存泄漏检测
2.1.检查是否有内存泄漏。
2.2.找到具体的内存泄漏位置。
3.Linux平台下的内存泄漏检测
4.摘要
其实Windows和Linux下的内存检测可以单独介绍,方法和工具远不止本文介绍的。我的方法不是最好的。如果你有更好的方法,请告诉我和大家。
1.内存泄漏的介绍和后果
在维基百科中,内存泄漏是这样定义的:在计算机科学中,内存泄漏是指程序由于疏忽或错误而未能释放不再使用的内存的情况。内存泄漏并不意味着内存的物理消失,而是应用程序分配了某段内存后,由于设计错误,在释放之前失去了对该段内存的控制,造成内存浪费。
最难以捉摸和最难检测的错误之一是内存泄漏,即无法正确释放先前分配的内存的错误。只发生一次的小内存泄漏可能不会被注意到,但是大量泄漏内存的程序或泄漏越来越多的程序可能会出现各种症状:从性能差(并逐渐减少)到内存完全耗尽。更糟糕的是,泄漏的程序可能会耗尽大量内存,导致另一个程序失败,这使得用户无法找到问题的真正根源。此外,即使无害的内存泄漏也可能是其他问题的迹象。
内存泄漏会减少可用内存量,从而降低计算机的性能。最终,在最坏的情况下,分配了太多的可用内存,这将导致所有或部分设备停止正常工作或应用程序崩溃。内存泄漏可能并不严重,甚至可以通过常规手段检测出来。在现代操作系统中,应用程序使用的常规内存在程序终止时被释放。意味着这个短时间运行的应用程序的内存泄漏不会导致严重的后果。
在下列情况下,内存泄漏会导致严重的后果:
程序运行后被忽略,随着时间的推移消耗的内存越来越多(比如服务器上的后台任务,尤其是嵌入式系统,运行后可能会被忽略很多年);
经常分配新的内存,例如在显示计算机游戏或动画视频图像时;
程序可以请求未释放的内存(如共享内存),即使当程序终止时;
操作系统内部发生泄漏;
系统的关键驱动发生泄漏;
内存非常有限,比如在嵌入式系统或者便携设备中;
在终止时内存不自动释放的操作系统(如AmigaOS)上运行时,一旦丢失,只能通过重启来恢复。
下面通过下面的例子来介绍如何检测内存泄漏:
?
一个
2
三
四
五
六
七
八
九
10
11
12
13
14
15
16
17
18
19
#包含stdlib.h
#包括iostream
使用命名空间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 内存泄漏测试!endl
//如果main中有while循环,则调用GetMemory
//那么问题就变得很严重了
//while(1){GetMemory(.);}
返回0;
}
其实不可能这么简单。如果这么简单,就不需要其他方法了。程序员一眼就能看出问题。该程序仅用于测试。
2.Windows平台下的内存泄漏检测
2.1.检查是否有内存泄漏。
Windows平台下的Visual Studio调试器和C运行时(CRT)库为我们提供了检测和识别内存泄漏的有效方法。原理大致如下:内存分配要通过CRT在运行时实现。只要分别记录内存分配和内存释放的记录,通过比较程序结束时内存分配和内存释放的记录,就可以确定是否存在内存泄漏。在vs中启用内存检测的方法如下:
第一步,在程序中包含以下语句:(#include语句必须按照上面显示的顺序。如果顺序改变,所使用的功能可能无法正常工作。)
?
一个
2
三
#define _CRTDBG_MAP_ALLOC
#包含stdlib.h
#包含crtdbg.h
包括crtdbg.h将malloc和free函数映射到它们的调试版本,即_malloc_dbg和_free_dbg,它们将跟踪内存分配和释放。这种映射只发生在调试版本中(其中定义了_DEBUG)。该版本使用正常的malloc和free函数。
#define语句将CRT堆函数的基本版本映射到相应的“调试”版本。这一陈述不是绝对必需的;但是如果没有这个语句,内存泄漏转储将包含较少的有用信息。
第二步。添加上述语句后,您可以通过在程序中包含以下语句来转储内存泄漏信息(通常在程序退出之前):
?
一个
_ CrtDumpMemoryLeaks();
至此,完整的代码如下:
查看代码?
一个
2
三
四
五
六
七
八
九
10
11
12
13
14
15
16
17
18
19
20
#define _CRTDBG_MAP_ALLOC
#包含stdlib.h
#包含crtdbg.h
#包括iostream
使用命名空间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 内存泄漏测试!endl
_ CrtDumpMemoryLeaks();
返回0;
}
在调试器下运行程序时,_ CrtDumpMemoryLeaks会在输出窗口中显示内存泄漏信息。内存泄漏信息如下:
图像
如果不使用#define _CRTDBG_MAP_ALLOC语句,内存泄漏转储将如下所示:
图像
如果未定义_CRTDBG_MAP_ALLOC,将显示以下内容:
内存分配号(在花括号中)。
块的类型(普通、客户端或CRT)。
“普通块”是由程序分配的普通内存。
客户端块是一种特殊类型的内存块,由MFC程序用于需要析构函数的对象。MFC new操作根据所创建对象的需要创建普通块或客户端块。
“CRT块”是CRT库分配给自己使用的内存块。CRT库处理这些块的释放,因此您不太可能在内存泄漏报告中看到这些块,除非出现严重错误(例如,CRT库损坏)。
您不会从内存泄漏信息中看到以下两种块类型:
“空闲块”是已经被释放的存储块。
“忽略的块”是您特别标记的块,因此它不会出现在内存泄漏报告中。
十六进制形式的内存位置。
以字节表示的块大小。
前16个字节的内容(也是十六进制)。
定义_CRTDBG_MAP_ALLOC时,还会显示分配泄漏内存的文件。文件名后括号中的数字(本例中为10)是文件中的行号。
注意:如果程序总是在同一个位置退出,那么调用_CrtDumpMemoryLeaks将会非常容易。如果程序从多个位置退出,则不需要在每个可能的退出位置调用_CrtDumpMemoryLeaks,但可以在程序的开头包含以下调用:
查看代码?
一个
_ CrtSetDbgFlag(_ CRT dbg _ ALLOC _ MEM _ DF _ CRT dbg _ LEAK _ CHECK _ DF);
该语句在程序退出时自动调用_CrtDumpMemoryLeaks。如前所示,必须同时设置两个位域_CRTDBG_ALLOC_MEM_DF和_CRTDBG_LEAK_CHECK_DF。
2.2.找到具体的内存泄漏位置。
通过上面的方法,我们几乎可以定位到内存分配函数malloc和new被调用的位置,比如上面例子中的GetMemory函数,也就是第10行!但是我们无法定位调用GetMemory()导致的内存泄漏,在大型项目中调用GetMemory的地方可能很多。如何定位调用GetMemory导致的内存泄漏?
另一种定位内存泄漏的技术包括在关键点拍摄应用程序内存状态的快照。CRT库提供了一个结构type _CrtMemState,可用于存储内存状态的快照:
?
一个
_CrtMemState s1、s2、S3;
若要在给定点拍摄内存状态的快照,请将_CrtMemState结构传递给_CrtMemCheckpoint函数。此函数用当前内存状态的快照填充此结构:
?
一个
_ CrtMemCheckpoint(S1);
通过将_CrtMemState结构的内容传递给_ crtmemdumpstats函数,可以随时转储该结构的内容:
?
一个
_ CrtMemDumpStatistics(S1);
要确定某部分代码中是否存在内存泄漏,可以拍摄该部分之前和之后的内存状态的快照,然后使用_CrtMemDifference来比较这两种状态:
?
一个
2
三
四
五
六
_ CrtMemCheckpoint(S1);
//内存分配发生在这里
_ CrtMemCheckpoint(S2);
if ( _CrtMemDifference( s3,s1,s2))
_ CrtMemDumpStatistics(S3);
顾名思义,_CrtMemDifference比较两个内存状态(s1和s2)并生成这两个状态之差的结果(s3)。将_CrtMemCheckpoint调用放在程序的开头和结尾,并将结果与_CrtMemDifference进行比较,这是检查内存泄漏的另一种方法。如果检测到泄漏,可以使用_CrtMemCheckpoint调用来划分程序,并通过二分搜索法技术定位泄漏。
在上面的示例程序中,我们可以找到调用GetMemory的确切位置,如下所示:
查看代码?
一个
2
三
四
五
六
七
八
九
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#define _CRTDBG_MAP_ALLOC
#包含stdlib.h
#包含crtdbg.h
#包括iostream
使用命名空间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 内存泄漏测试!endl
_ CrtDumpMemoryLeaks();
返回0;
}
调试时,程序输出以下结果:
图像
这说明s1和s2之间存在内存泄漏!如果在s1和s2之间没有调用GetMemory,那么就不会有信息输出。
3.Linux平台下的内存泄漏检测
上面我们介绍了在vs中,“crtdbg.h包含在代码中,malloc和free函数映射到它们的调试版本,分别是_malloc_dbg和_free_dbg。这两个函数将跟踪内存分配和释放。这种映射只发生在调试版本中(其中定义了_DEBUG)。该版本使用正常的malloc和free函数。也就是malloc和free挂钩记录内存分配信息。
Linux和下面这个方法原理一样:——mtrace,http://en.wikipedia.org/wiki/Mtrace.方法差不多,就不详细描述了。参与给定的链接。本节主要介绍一个非常强大的工具,valgrind。如下图所示:
图像
如上图所示:
==6118==1个数据块中的100个字节肯定在1个丢失记录1中丢失
==6118==at0x 4024 f20:malloc(VG _ replace _ malloc . c:236)
==6118==by0x 8048724:GetMemory(char *,int)(in/home/netsky/workspace/a . out)
==6118==by0x 804874 e:main(in/home/netsky/workspace/a . out)
在main中调用GetMemory导致内存泄漏,在GetMemory中调用malloc导致100字节内存泄漏。
注意事项:
每个错误信息中包含大量信息;仔细阅读。
6118是进程ID;通常不重要。
第一行("堆摘要")告诉您这是哪种错误。
第一行下面是堆栈跟踪,告诉您问题发生在哪里。堆栈跟踪可能会变得非常大
令人困惑,尤其是当你使用C STL时。自下而上阅读会有所帮助。
代码地址(如0x4024F20)通常不重要,但偶尔对追踪更奇怪的人至关重要
虫子。
堆栈跟踪告诉您泄漏的内存是在哪里分配的Memcheck。无法告诉您内存泄漏的原因,
不幸的是。(忽略VG _ replace _ malloc。c ,那是实现细节。)
泄露有几种情况;两个最重要的类别是:
"彻底丢失":你的程序正在泄漏内存——修复它!
"可能丢失":你的程序正在泄漏内存,除非你在用指针做一些有趣的事情(比如移动
它们指向堆块的中间)
瓦尔格兰的使用请见手册http://valgrind.org/docs/manual/manual.html。
4、总结
其实内存泄漏的原因可以概括为:调用了malloc/新等内存申请的操作,但缺少了对应的自由/删除,总之就是,malloc/new比自由/删除的数量多。我们在编程时需要注意这点,保证每个分配内存都有对应的免费,每个新的都有对应的删除了!平时要养成这样一个好的习惯。
要避免内存泄漏可以总结为以下几点:
程序员要养成良好习惯,保证malloc/新和自由/删除匹配;
检测内存泄漏的关键原理就是,检查malloc/新和自由/删除是否匹配,一些工具也就是这个原理。要做到这点,就是利用宏或者钩子,在用户程序与运行库之间加了一层,用于记录内存分配情况。
作者:吴秦
出处:http://www.cnblogs.com/skynet/
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。