内存泄漏概念
维基百科中这样解释:在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
内存泄漏通常情况下只能由获得程序源代码的程序员才能分析出来。因此在一个具有几十万行源代码的项目中寻找内存泄露无异于大海捞针,因此对于内存泄漏问题不可轻视。
内存泄漏的后果
内存泄漏会因为减少可用内存的数量从而降低计算机的性能。最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃。
内存泄漏带来的后果可能是不严重的,有时甚至能够被常规的手段检测出来。在现代操作系统中,一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂运行的应用程序中的内存泄漏不会导致严重后果。
在以下情况,内存泄漏导致较严重的后果:
- 程序运行后置之不理,并且随着时间的流逝消耗越来越多的内存(比如服务器上的后台任务,尤其是嵌入式系统中的后台任务,这些任务可能被运行后很多年内都置之不理);
- 新的内存被频繁地分配,比如当显示计算机游戏或动画视频画面时;
- 程序能够请求未被释放的内存(比如共享内存),甚至是在程序终止的时候;
- 泄漏在操作系统内部发生;
- 泄漏在系统关键驱动中发生;
- 内存非常有限,比如在嵌入式系统或便携设备中;
- 当运行于一个终止时内存并不自动释放的操作系统(比如AmigaOS)之上,而且一旦丢失只能通过重启来恢复。
内存泄漏分类
C/C++程序中一般我们关心两种方面的内存泄漏:
- 堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会出现Heap Leak。
- 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
这里需要注意,指针和它所指向的东西是完全不同的。 局部变量在函数返回时就会释放, 但是在指针变量这个问题上, 这表示指针被释放, 而不是它所指向的对象。 用 malloc() 分配的内存直到你明确释放它之前都会保留在那里。 一般地, 对于每一个 malloc() (new)都必须有个对应的 free() (delete)调用。同时也要注意异常安全问题。
如何检测内存泄漏
- 在linux下内存泄漏检测的检测(Valgrind(http://valgrind.org/docs/manual/manual.html)
- 在windows下使用第三方工具:VLD工具(http://vld.codeplex.com/)
- 其他工具
检测内存泄露的工具有很多,大家可以根据不同的需要参考官方的说明文档,这里我就不在赘述了。这里只简单介绍最简单的检测方法(检测最简单的内存泄漏问题),让我们对检测内存泄露的方式和方法有一个基本的认识。
Windows下内存泄漏检测
Windows平台下面Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法,原理大致如下:内存分配要通过CRT在运行时实现,只要在分配内存和释放内存时分别做好记录,程序结束时对比分配内存和释放内存的记录就可以确定是不是有内存泄漏。在vs中启用内存检测的方法如下:
#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);//使用new也能够检测出来
}
int main()
{
char *str = NULL;
GetMemory(str, 1000);
//while (1) { GetMemory(...); }
//如果main中存在while循环调用GetMemory,那么问题将变得很严重
cout << "Memory leak test!" << endl;
//加入下面的代码来报告内存泄漏信息
_CrtDumpMemoryLeaks();
system("pause");
return 0;
}
注:头文件必须保证上面声明的顺序,如果改变了顺序,可能不能正常工作。
<crtdbg.h>的_malloc_dbg和_free_dbg将取代标准的malloc和free函数出现在DEBUG版中,它可以跟踪内存的分配和释放。但是这只会在DEBUG版本中发生(当#define _DEBUG的时候),而Release版本仍使用标准的malloc和free功能。
#define _CRTDBG_MAP_ALLOC表示使用CRT堆函数的相应的DEBUG版本。这个定义不是必须的,但是没有它,内存泄漏报告含有的只是没有什么用处的信息。
当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在“输出”窗口中显示内存泄漏信息。 内存泄漏信息如下所示
- 内存分配数值(花括号内)
- 模块的类型(normal、client或者CRT)
- 以十六进制格式定位的内存
- 以字节计模块的大小
- 第一个十六字节的内容(也可以用十六进制)
如果定义了_CRTDBG_MAP_ALLOC,报告的内容还包括出现分配所泄漏内存的文件。在文件名之后括号内的数字是文件内的行数值。
解释内存模块的类型
内存泄漏报告中把每一块泄漏的内存分为普通块、客户块和CRT块。事实上,你只需要留心普通块和客户块类型。
- 普通块(normal block):是由你的程序分配的内存。
- 客户块(client block):是一种特殊的内存块,它是由MFC使用的一个对象,程序退出时,该对象的析构函数没有被调用。MFC new操作符可以用来创建普通块和客户块。
- CRT块(CRT block):是由C RunTime Library供自己使用而分配的内存块。CRT库自己来管理这些内存的分配与释放,通常你不会在内存泄漏报告中发现有CRT内存泄漏,除非程序发生了严重的错误(例如CRT库崩溃)。
下面这两种类型的内存块不会出现在内存泄漏报告中:
- 空闲块(free block):已经被释放(free)的内存块。
- 忽略块(Ignore block):是程序员显式声明过不要在内存泄漏报告中出现的内存块
设置CRT报告样式
通常_CrtDumpMemoryLeaks()会dump内存泄漏的信息到output窗口的Debug栏位。你可以使用_CrtSetReportMode()来重新设置输出到另一个位置。关于更详细的如何使用_CrtSetReportMode()说明,请查看MSDN。
使用_CrtSetDbgFlag
如果你的程序只在一个地方退出,那么在选择调用_CrtDumpMemoryLeaks的位置是非常容易的。但是,如果你的程序可能会在程序多处位置退出该怎么办?如果不希望在每一个可能的出口处调用_CrtDumpMemoryLeaks,那么你可以在你的程序开始处包含下面的调用:
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
当程序退出时,将会自动地调用_CrtDumpMemoryLeaks(必须设置_CRTDBG_ALLOC_MEM_DF和 _CRTDBG_LEAK_CHECK_DF)。
Linux平台下的内存泄漏检测
Linux下面也有和Windows下原理相同的方法——mtrace,大家可以参考http://en.wikipedia.org/wiki/Mtrace。我着重介绍一下工具valgrind。演示如下:
测试代码:
#include<stdio.h>
#include<malloc.h>
void GetMemory(char *p,int num)
{
p=(char*)malloc(sizeof(char)*num);
}
int main()
{
char *str=NULL;
GetMemory(str,100);
return 0;
}
输出结果:
由上图可获取以下结果:
==2035== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2035== at 0x4C29BC3: malloc (vg_replace_malloc.c:299)
==2035== by 0x400538: GetMemory (in /home/zhitou/linuxsp/a.out)
==2035== by 0x40055F: main (in /home/zhitou/linuxsp/a.out)
是在main中调用了GetMemory导致的内存泄漏,GetMemory中是调用了malloc导致泄漏了100字节的内存。
如何避免内存泄露
- 养成良好的编码规范,申请的内存空间记着匹配的去释放。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证;
- 采用RAII思想或者智能指针来管理资源;
- 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项;
- 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵;
- 内存泄漏非常常见,解决方案分为两种:
- 事前预防型。如智能指针等。
- 事后查错型。如泄漏检测工具。