一、内存泄露的定义
小程序
通常咱们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小能够在程序运行期决定),使用完后必须显示释放的内存。数组
应用程序通常使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,不然,这块内存就不能被再次使用,咱们就说这块内存泄漏了。如下这段小程序演示了堆内存发生泄漏的情形:服务器
1 void MyFunction(int nSize) 2 { 3 char* p= new char[nSize]; 4 if( !GetStringFrom( p, nSize ) ){ 5 MessageBox(“Error”); 6 return; 7 } 8 9 …//using the string pointed by p; 10 delete p; 11 }
当函数GetStringFrom()返回零的时候,指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存,可是c函数能够在任何地方退出,因此一旦有某个出口处没有释放应该释放的内存,就会发生内存泄漏。函数
广义的说,内存泄漏不只仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),好比核心态HANDLE,GDI Object,SOCKET, Interface等,从根本上说这些由操做系统分配的对象也消耗内存,若是这些对象发生泄漏最终也会致使内存的泄漏。并且,某些对象消耗的是核心态内存,这些对象严重泄漏时会致使整个操做系统不稳定。因此相比之下,系统资源的泄漏比堆内存的泄漏更为严重。性能
GDI Object的泄漏是一种常见的资源泄漏:动画
1 void CMyView::OnPaint( CDC* pDC ) 2 { 3 CBitmap bmp; 4 CBitmap* pOldBmp; 5 bmp.LoadBitmap(IDB_MYBMP); 6 pOldBmp = pDC->SelectObject( &bmp ); 7 8 9 if( Something() ){ 10 return; 11 } 12 pDC->SelectObject( pOldBmp ); 13 return; 14 }
当函数Something()返回非零的时候,程序在退出前没有把pOldBmp选回pDC中,这会致使pOldBmp指向的HBITMAP对象发生泄漏。这个程序若是长时间的运行,可能会致使整个系统花屏。这种问题在Win9x下比较容易暴露出来,由于Win9x的GDI堆比Win2k或NT的要小不少。spa
二、内存泄漏后果:操作系统
只发生一次的小的内存泄漏可能不会被注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各类征兆:从性能不良(而且逐渐下降)到内存彻底用尽。指针
更糟的是,泄漏的程序可能会用掉太多内存,以至另外一个程序失败,而使用户无从查找问题的真正根源。 此外,即便无害的内存泄漏也多是其余问题的征兆。内存泄漏会由于减小可用内存的数量从而下降计算机的性能。code
内存泄漏也会致使较严重的后果:
三、内存泄漏的几种状况:
(1)在类的构造函数和析构函数中没有匹配的调用new和delete函数
两种状况下会出现这种内存泄露:
一是在堆里建立了对象占用了内存,可是没有显示地释放对象占用的内存;
二是在类的构造函数中动态的分配了内存,可是在析构函数中没有释放内存或者没有正确的释放内存;
(2)没有正确的清除嵌套对象的指针
(3)在释放对象数组时在delete中没有使用方括号
方括号是告诉编译器这个指针指向的是一个对象数组,同时也告诉编译器正确的对象地址值病调用对象的析构函数,若是没有方括号,那么这个指针就被默认为只指向一个对象,对象数组中的其余对象的析构函数就不会被调用,结果形成了内存泄露。
若是在方括号中间放了一个比对象数组大小还大的数字,那么编译器就会调用无效对象(内存溢出)的析构函数,会形成堆的奔溃。若是方括号中间的数字值比对象数组的大小小的话,编译器就不能调用足够多个析构函数,结果会形成内存泄露。
释放单个对象、单个基本数据类型的变量或者是基本数据类型的数组不须要大小参数,释放定义了析构函数的对象数组才须要大小参数。
(4)指向对象的指针数组不等同于对象数组
对象数组是指:数组中存放的是对象,只须要delete []p,便可调用对象数组中的每一个对象的析构函数释放空间;
指向对象的指针数组是指:数组中存放的是指向对象的指针,不只要释放每一个对象的空间,还要释放每一个指针的空间,delete []p只是释放了每一个指针,可是并无释放对象的空间,正确的作法,是经过一个循环,将每一个对象释放了,而后再把指针释放了;
(5) 缺乏拷贝构造函数
两次释放相同的内存是一种错误的作法,同时可能会形成堆的崩溃。按值传递会调用(拷贝)构造函数,引用传递不会调用。
在C++中,若是没有定义拷贝构造函数,那么编译器就会调用默认的拷贝构造函数,会逐个成员拷贝的方式来复制数据成员,若是是以逐个成员拷贝的方式来复制指针被定义为将一个变量的地址赋给另外一个变量。
这种隐式的指针复制结果就是两个对象拥有指向同一个动态分配的内存空间的指针:
当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态分配的内存空间。而释放第二个对象的时候,它的析构函数会释放相同的内存,这样是错误的。因此,若是一个类里面有指针成员变量,要么必须显示的写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符
(6) 缺乏重载赋值运算符
这种问题跟上述问题相似,也是逐个成员拷贝的方式复制对象,若是这个类的大小是可变的,那么结果就是形成内存泄露;
(7) 关于nonmodifying运算符重载的常见迷思
a. 返回栈上对象的引用或者指针(也即返回局部对象的引用或者指针)。致使最后返回的是一个空引用或者空指针,所以变成野指针;
b. 返回内部静态对象的引用;
c. 返回一个泄露内存的动态分配的对象。致使内存泄露,而且没法回收;
解决这一类问题的办法是重载运算符函数的返回值不是类型的引用,二应该是类型的返回值,即不是 int&而是int;
(8)没有将基类的析构函数定义为虚函数
当基类指针指向子类对象时,若是基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,所以形成内存泄露;
四、野指针
野指针:指向被释放的或者访问受限内存的指针。
形成野指针的缘由:
1. 指针变量没有被初始化(若是值不定,能够初始化为NULL);
2. 指针被free或者delete后,没有置为NULL, free和delete只是把指针所指向的内存给释放掉,并无把指针自己干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL;
3. 指针操做超越了变量的做用范围,好比返回指向栈内存的指针就是野指针;
内存泄露总结:
其实内存泄漏的缘由能够归纳为:调用了malloc/new等内存申请的操做,但缺乏了对应的free/delete释放操做,总之就是,malloc/new比free/delete的数量多。内存用完,再也不使用要及时释放。