摘自《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++标准中,通过基类的指针去删除子类的对象,而基类又没有定义虚析构函数时,结果将是不确定的。