内存泄漏以及常见的解决方法

 之因此撰写这篇文章是由于前段时间花费了很大的精力在已经成熟的代码上再去处理memory leak问题。写此的目的是但愿咱们应该养成良好的编码习惯,尽量的避免这样的问题,由于当你对着一大片的代码再去处理此类的问题,此时无疑增长了解决的成本和难度。准确的说属于补救措施了。
1. 什么是内存泄漏(memory leak)?

 指因为疏忽或错误形成程序未能释放已经再也不使用的内存的状况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,于是形成了内存的浪费。 


2. 对于C和C++这种没有Garbage Collection 的语言来说,咱们主要关注两种类型的内存泄漏:

   堆内存泄漏(Heap leak)。对内存指的是程序运行中根据须要分配经过malloc,realloc new等从堆中分配的一块内存,再是完成后必须经过调用对应的 free或者delete 删掉。若是程序的设计的错误致使这部份内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak. php

  系统资源泄露(Resource Leak).主要指程序使用系统分配的资源好比 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,致使系统资源的浪费,严重可致使系统效能下降,系统运行不稳定。  

3. 如何解决内存泄露?

内存泄露的问题其困难在于
1.编译器不能发现这些问题。
2.运行时才能捕获到这些错误,这些错误没有明显的症状,时隐时现。
3.对于手机等终端开发用户来讲,尤其困难。下面从三个方面来解决内存泄露:

第一,良好的编码习惯,尽可能在涉及内存的程序段,检测出内存泄露。当程式稳定以后,在来检测内存泄露时,无疑增长了排除的困难和复杂度。

使用了内存分配的函数,要记得要使用其想用的函数释放掉,一旦使用完毕。

吴秦(Tyler)

“该死系统存在内存泄漏问题”,项目中因为各方面因素,老是有人抱怨存在内存泄漏,系统长时间运行以后,可用内存愈来愈少,甚至致使了某些服务失败。内存泄漏是最难发现的常见错误之一,由于除非用完内存或调用malloc失败,不然都不会致使任何问题。实际上,使用C/C++这类没有垃圾回收机制的语言时,你不少时间都花在处理如何正确释放内存上。若是程序运行时间足够长,如后台进程运行在服务器上,只要服务器不宕机就一直运行,一个小小的失误也会对程序形成重大的影响,如形成某些关键服务失败。html

对于内存泄漏,本人深有体会!实习的时候,公司一个项目中就存在内存泄漏问题,项目的代码两很是大,后台进程也比较多,形成内存泄漏的地方比较难找。此次机会是我对如何查找内存泄漏问题,有了必定的经验,后面本身的作了相关实验,在此我分享一下内存泄漏如何调试查找,主要内容以下:ios

  • 一、内存泄漏简介
  • 二、Windows平台下的内存泄漏检测
    • 2.一、检测是否存在内存泄漏问题
    • 2.二、定位具体的内存泄漏地方
  • 三、Linux平台下的内存泄漏检测 
  • 四、总结

其实Windows、Linux下面的内存检测均可以单独开篇详细介绍,方法和工具也远远不止文中介绍到的,个人方法也不是最优的,若是您有更好的方法,也请您告诉我和你们。程序员

一、内存泄漏简介及后果

wikipedia中这样定义内存泄漏:在计算机科学中,内存泄漏指因为疏忽或错误形成程序未能释放已经再也不使用的内存的状况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,致使在释放该段内存以前就失去了对该段内存的控制,从而形成了内存的浪费。编程

最难捉摸也最难检测到的错误之一是内存泄漏,即未能正确释放之前分配的内存的 bug。 只发生一次的小的内存泄漏可能不会被注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各类征兆:从性能不良(而且逐渐下降)到内存彻底用尽。 更糟的是,泄漏的程序可能会用掉太多内存,以至另外一个程序失败,而使用户无从查找问题的真正根源。 此外,即便无害的内存泄漏也多是其余问题的征兆。服务器

内存泄漏会由于减小可用内存的数量从而下降计算机的性能。最终,在最糟糕的状况下,过多的可用内存被分配掉致使所有或部分设备中止正常工做,或者应用程序崩溃。内存泄漏可能不严重,甚至可以被常规的手段检测出来。在现代操做系统中,一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂运行的应用程序中的内存泄漏不会致使严重后果。less

在如下情況,内存泄漏致使较严重的后果:函数

  • 程序运行后置之不理,而且随着时间的流失消耗愈来愈多的内存(好比服务器上的后台任务,尤为是嵌入式系统中的后台任务,这些任务可能被运行后不少年内都置之不理);
  • 新的内存被频繁地分配,好比当显示电脑游戏或动画视频画面时;
  • 程序可以请求未被释放的内存(好比共享内存),甚至是在程序终止的时候;
  • 泄漏在操做系统内部发生;
  • 泄漏在系统关键驱动中发生;
  • 内存很是有限,好比在嵌入式系统或便携设备中;
  • 当运行于一个终止时内存并不自动释放的操做系统(好比AmigaOS)之上,并且一旦丢失只能经过重启来恢复。

下面咱们经过如下例子来介绍如何检测内存泄漏问题:工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
#include <iostream>
using  namespace  std;
 
void  GetMemory( char  *p, int  num)
{
     p = ( char *) malloc ( sizeof ( char ) * num); //使用new也可以检测出来
}
 
int  main( int  argc, char ** argv)
{
     char  *str = NULL;
     GetMemory(str, 100);
     cout<< "Memory leak test!" <<endl;
     //若是main中存在while循环调用GetMemory
     //那么问题将变得很严重
     //while(1){GetMemory(...);}
     return  0;
}

实际中不可能这么简单,若是这么简单也用不着别的方法,程序员一眼就能够看出问题,此程序只用于测试。post

二、Windows平台下的内存泄漏检测

2.一、检测是否存在内存泄漏问题

Windows平台下面Visual Studio 调试器和 C 运行时 (CRT) 库为咱们提供了检测和识别内存泄漏的有效方法,原理大体以下:内存分配要经过CRT在运行时实现,只要在分配内存和释放内存时分别作好记录,程序结束时对比分配内存和释放内存的记录就能够肯定是否是有内存泄漏。在vs中启用内存检测的方法以下:

  • STEP1,在程序中包括如下语句: (#include 语句必须采用上文所示顺序。 若是更改了顺序,所使用的函数可能没法正常工做。)
1
2
3
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

经过包括 crtdbg.h,将 malloc 和 free 函数映射到它们的调试版本,即 _malloc_dbg 和 _free_dbg,这两个函数将跟踪内存分配和释放。 此映射只在调试版本(在其中定义了_DEBUG)中发生。 发布版本使用普通的 malloc 和 free 函数。

#define 语句将 CRT 堆函数的基版本映射到对应的“Debug”版本。 并不是绝对须要该语句;但若是没有该语句,内存泄漏转储包含的有用信息将较少。

  • STEP2, 在添加了上述语句以后,能够经过在程序中包括如下语句(一般应刚好放在程序退出位置以前)来转储内存泄漏信息:
1
_CrtDumpMemoryLeaks();

此时,完整的代码以下:

当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在“输出”窗口中显示内存泄漏信息。 内存泄漏信息以下所示:

image

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

image

未定义 _CRTDBG_MAP_ALLOC 时,所显示的会是:

  • 内存分配编号(在大括号内)。

  • 块类型(普通、客户端或 CRT)。

  • “普通块”是由程序分配的普通内存。

  • “客户端块”是由 MFC 程序用于须要析构函数的对象的特殊类型内存块。 MFC new 操做根据正在建立的对象的须要建立普通块或客户端块。

  • “CRT 块”是由 CRT 库为本身使用而分配的内存块。 CRT 库处理这些块的释放,所以您不大可能在内存泄漏报告中看到这些块,除非出现严重错误(例如 CRT 库损坏)。

从不会在内存泄漏信息中看到下面两种块类型:

  • “可用块”是已释放的内存块。

  • “忽略块”是您已特别标记的块,于是不出如今内存泄漏报告中。

  • 十六进制形式的内存位置。

  • 以字节为单位的块大小。

  • 前 16 字节的内容(亦为十六进制)。

定义了 _CRTDBG_MAP_ALLOC 时,还会显示在其中分配泄漏的内存的文件。 文件名后括号中的数字(本示例中为 10)是该文件中的行号。

注意:若是程序老是在同一位置退出,调用 _CrtDumpMemoryLeaks 将很是容易。 若是程序从多个位置退出,则无需在每一个可能退出的位置放置对_CrtDumpMemoryLeaks 的调用,而能够在程序开始处包含如下调用:

该语句在程序退出时自动调用 _CrtDumpMemoryLeaks。 必须同时设置 _CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF 两个位域,如前面所示。

2.二、定位具体的内存泄漏地方

经过上面的方法,咱们几乎能够定位到是哪一个地方调用内存分配函数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 比较这两个状态:

1
2
3
4
5
6
_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );
 
if  ( _CrtMemDifference( &s3, &s1, &s2) )
    _CrtMemDumpStatistics( &s3 );

顾名思义,_CrtMemDifference 比较两个内存状态(s1 和 s2),生成这两个状态之间差别的结果(s3)。 在程序的开始和结尾放置 _CrtMemCheckpoint 调用,并使用_CrtMemDifference 比较结果,是检查内存泄漏的另外一种方法。 若是检测到泄漏,则可使用 _CrtMemCheckpoint 调用经过二进制搜索技术来划分程序和定位泄漏。

如上面的例子程序咱们能够这样来定位确切的调用GetMemory的地方:

调试时,程序输出以下结果:

image

这说明在s1和s2之间存在内存泄漏!!!若是GetMemory不是在s1和s2之间调用,那么就不会有信息输出。

三、Linux平台下的内存泄漏检测

在上面咱们介绍了,vs中在代码中“包含crtdbg.h,将 malloc 和 free 函数映射到它们的调试版本,即 _malloc_dbg 和 _free_dbg,这两个函数将跟踪内存分配和释放。 此映射只在调试版本(在其中定义了_DEBUG)中发生。 发布版本使用普通的 malloc 和 free 函数。”即为malloc和free作了钩子,用于记录内存分配信息。

Linux下面也有原理相同的方法——mtrace,http://en.wikipedia.org/wiki/Mtrace。方法相似,我这就不具体描述,参加给出的连接。这节我主要介绍一个很是强大的工具valgrind。以下图所示:

image

如上图所示知道:

==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字节的内存。

Things to notice:  • There is a lot of information in each error message; read it carefully.  • The 6118 is the process ID; it’s usually unimportant.  • The first line ("Heap Summary") tells you what kind of error it is.  • Below the first line is a stack trace telling you where the problem occurred. Stack traces can get quite large, and be  confusing, especially if you are using the C++ STL. Reading them from the bottom up can help.

• The code addresses (eg. 0x4024F20) are usually unimportant, but occasionally crucial for tracking down weirder  bugs.

The stack trace tells you where the leaked memory was allocated. Memcheck cannot tell you why the memory leaked,  unfortunately. (Ignore the "vg_replace_malloc.c", that’s an implementation detail.)  There are several kinds of leaks; the two most important categories are:  • "definitely lost": your program is leaking memory -- fix it!  • "probably lost": your program is leaking memory, unless you’re doing funny things with pointers (such as moving  them to point to the middle of a heap block)

Valgrind的使用请见手册http://valgrind.org/docs/manual/manual.html

四、总结

其实内存泄漏的缘由能够归纳为:调用了malloc/new等内存申请的操做,但缺乏了对应的free/delete,总之就是,malloc/new比free/delete的数量多。咱们在编程时须要注意这点,保证每一个malloc都有对应的free,每一个new都有对应的deleted!!!平时要养成这样一个好的习惯。

要避免内存泄漏能够总结为如下几点:

  • 程序员要养成良好习惯,保证malloc/new和free/delete匹配;
  • 检测内存泄漏的关键原理就是,检查malloc/new和free/delete是否匹配,一些工具也就是这个原理。要作到这点,就是利用宏或者钩子,在用户程序与运行库之间加了一层,用于记录内存分配状况。

做者:吴秦 出处:http://www.cnblogs.com/skynet/