摘自《c/c++中常见内存泄露与对策及预防措施浅析》
1 内存泄漏的发生方式 以发生的方式来分类,内存泄漏可以分为以下四类。
(1)常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行时候都会导致一块内存泄漏。
(2)偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程中才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
(3)一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致有且仅有一块内存发生泄漏。
(4)隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格地说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存,但是对于一个服务器程序,需要运行几天几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存,所以称这类内存泄漏为隐式内存泄漏。
2 常见内存泄露与对策
(1)在使用局部指针变量或静态指针变量中的内存泄漏。
例如:
int main() { int *pi; pi=new int[100]; r e tu rn; } 这是常见的使用局部变量时出现的内存泄漏,没有释放指针变量。再如: int main() { static int *pi=0; for(int i=0;i<10;i++) pi=new int; r e tu rn; } 这是常见的使用静态指针变量时出现的内存泄漏。程序退出后,分配的10个int只有最后一个是reachable的,而前面9个int则泄漏了。 对策:释放全部指针变量。(2)在使用动态分配内存空间中的内存泄漏。
例如:
void MyFunction(int nSize) { char *p= new char[nSize]; if(!GetString(p,nSize)) { messageBox(“Error”); return; } ⋯ ⋯ delete p; } 这是一个简单而又典型的内存泄漏示例,当函数GetSt ring( )调用错误时,函数MyFunction结束而指针指向的内存却没有 被释放,此时便出现了内存泄漏。在程序段入口处分配内存,在出口处释放内存,但是函数可以在任何地方退出,所以一旦有某个出口处没有释放应该释放的内存,就很容易发生内存泄漏。 对策:C/C++函数可以在任何地方退出,在每个出口处释放应该释放的内存。本例中只要在return这个出口处释放内存p即可。(3)重复分配内存发生的内存泄漏。
例如:
void MyFunction(int nSize) { ⋯ ⋯ char *p= new char[nSize]; char *p= new char[nSize]; ⋯ ⋯ } 对策:重复分配内存,第一块内存永远无法使用。这种情况一般多发生在编码过程中使用“代码复制”时出现错误,因此复 制代码要谨慎。(4)非空指针被重新赋值发生的内存泄漏。
给指针赋值时,没有检查指针是否为空,如果指针不空,那么指针原来指向的内存将泄漏。例如: char *p= new char[nSize]; q = p ; 对策:当q指针不为空,对其重新赋值后,q以前指向的内存泄漏。本例中可以再动态分配一个空指针,使p指向它。(5)缺少else处理分支导致的内存泄漏。
例如: char *p= new char[nSize]; iCount= SortProc(p); if(iCount<= 0) ret= 0; else if(iCount<= 5) ret= DealProc(p,iCount-1); if(ret<= 0) delete p; 对策:当iCount>5时,ret取值不确定,当ret大于0时,没有释放p,造成内存泄漏。本例中需添加两个el se语句,用来判断 iCount >5时ret的取值,以及ret大于0时,释 放p。(6)删除指针顺序错误导致的内存泄漏。
例如:
Test ::~ Tes t() { delete(p); i f (NULL!= p && NULL!= p -> pData) { f ree(p->pData) ; } } 对策:当p已经被删除了,那么if条件永远不成立,于是这条free语句永远不会被执行,即p->pData占用的内存没有被释放。本例中应调整删除指针顺序。(7)析构函数忘记释放内存导致的内存泄漏。
例如:
Test ::~ Tes t() { } 对策:在析构函数里面对资源释放(非静态成员指针或资源)或清零(静态成员指针或资源)是一个良好的习惯,否则容易产 生内存泄漏。(8)基类没有定义虚析构函数引起的内存泄漏。
例如:
classA { ~A(){}//析构函数不是虚函数 } classB:publicA// classB继承了classA { ~B(){} } void main() { A *p = new B; delete p;}
对策“: delete p”调用了classA的析构函数,没有调用B的析构函数,导致classB里面申请的资源泄漏。因为在C++标准中,通过基类的指针去删除子类的对象,而基类又没有定义虚析构函数时,结果将是不确定的。