副标题#e#
浅谈内存泄漏
对付一个c/c++措施员来说,内存泄漏是一个常见的也是令人头疼的问题。已经有很多技能被研究出来以应对这个问题,好比Smart Pointer,Garbage Collection等。Smart Pointer技能较量成熟,STL中已经包括支持Smart Pointer的class,可是它的利用好像并不遍及,并且它也不能办理所有的问题;Garbage Collection技能在Java中已经较量成熟,可是在c/c++规模的成长并不顺畅,固然很早就有人思考在C++中也插手GC的支持。现实世界就是这样的,作为一个c/c++措施员,内存泄漏是你心中永远的痛。不外亏得此刻有很多东西可以或许辅佐我们验证内存泄漏的存在,找出产生问题的代码。
内存泄漏的界说
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指措施从堆中分派的,巨细任意的(内存块的巨细可以在措施运行期抉择),利用完后必需显示释放的内存。应用措施一般利用malloc,realloc,new等函数从堆中分派到一块内存,利用完后,措施必需认真相应的挪用free或delete释放该内存块,不然,这块内存就不能被再次利用,我们就说这块内存泄漏了。以下这段小措施演示了堆内存产生泄漏的景象:
void MyFunction(int nSize)
{
char* p= new char[nSize];
if( !GetStringFrom( p, nSize ) ){
MessageBox(“Error”);
return;
}
…//using the string pointed by p;
delete p;
}
例一
当函数GetStringFrom()返回零的时候,指针p指向的内存就不会被释放。这是一种常见的产生内存泄漏的景象。措施在进口处分派内存,在出口处释放内存,可是c函数可以在任那里所退出,所以一旦有某个出口处没有释放应该释放的内存,就会产生内存泄漏。
广义的说,内存泄漏不只仅包括堆内存的泄漏,还包括系统资源的泄漏(resource leak),好比焦点态HANDLE,GDI Object,SOCKET, Interface等,从基础上说这些由操纵系统分派的工具也耗损内存,假如这些工具产生泄漏最终也会导致内存的泄漏。并且,某些工具耗损的是焦点态内存,这些工具严重泄漏时会导致整个操纵系统不不变。所以对比之下,系统资源的泄漏比堆内存的泄漏更为严重。
GDI Object的泄漏是一种常见的资源泄漏:
void CMyView::OnPaint( CDC* pDC )
{
CBitmap bmp;
CBitmap* pOldBmp;
bmp.LoadBitmap(IDB_MYBMP);
pOldBmp = pDC->SelectObject( &bmp );
…
if( Something() ){
return;
}
pDC->SelectObject( pOldBmp );
return;
}
当函数Something()返回非零的时候,措施在退出前没有把pOldBmp选回pDC中,这会导致pOldBmp指向的HBITMAP工具产生泄漏。这个措施假如长时间的运行,大概会导致整个系统花屏。这种问题在Win9x下较量容易袒暴露来,因为Win9x的GDI堆比Win2k或NT的要小许多。
#p#副标题#e#
内存泄漏的产生方法:
以产生的方法来分类,内存泄漏可以分为4类:
1. 常发性内存泄漏。产生内存泄漏的代码会被多次执行到,每次被执行的时候城市导致一块内存泄漏。好比例二,假如Something()函数一直返回True,那么pOldBmp指向的HBITMAP工具老是产生泄漏。
2. 偶发性内存泄漏。产生内存泄漏的代码只有在某些特定情况或操纵进程下才会产生。好比例二,假如Something()函数只有在特定情况下才返回True,那么pOldBmp指向的HBITMAP工具并不老是产生泄漏。常发性和偶发性是相对的。对付特定的情况,偶发性的也许就酿成了常发性的。所以测试情况和测试要领对检测内存泄漏至关重要。
3. 一次性内存泄漏。产生内存泄漏的代码只会被执行一次,可能由于算法上的缺陷,导致总会有一块仅且一块内存产生泄漏。好比,在类的结构函数中分派内存,在析构函数中却没有释放该内存,可是因为这个类是一个Singleton,所以内存泄漏只会产生一次。另一个例子:
char* g_lpszFileName = NULL;
void SetFileName( const char* lpcszFileName )
{
if( g_lpszFileName ){
free( g_lpszFileName );
}
g_lpszFileName = strdup( lpcszFileName );
}
例三
假如措施在竣事的时候没有释放g_lpszFileName指向的字符串,那么,纵然多次挪用SetFileName(),总会有一块内存,并且仅有一块内存产生泄漏。
4. 隐式内存泄漏。措施在运行进程中不断的分派内存,可是直到竣事的时候才释放内存。严格的说这里并没有产生内存泄漏,因为最终措施释放了所有申请的内存。可是对付一个处事器措施,需要运行几天,几周甚至几个月,不实时释放内存也大概导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。举一个例子:
#p#分页标题#e#
class Connection
{
public:
Connection( SOCKET s);
~Connection();
…
private:
SOCKET _socket;
…
};
class ConnectionManager
{
public:
ConnectionManager(){
}
~ConnectionManager(){
list ::iterator it;
for( it = _connlist.begin(); it != _connlist.end(); ++it ){
delete (*it);
}
_connlist.clear();
}
void OnClientConnected( SOCKET s ){
Connection* p = new Connection(s);
_connlist.push_back(p);
}
void OnClientDisconnected( Connection* pconn ){
_connlist.remove( pconn );
delete pconn;
}
private:
list _connlist;
};
例四
假设在Client从Server端断开后,Server并没有呼唤OnClientDisconnected()函数,那么代表那次毗连的Connection工具就不会被实时的删除(在Server措施退出的时候,所有Connection工具会在ConnectionManager的析构函数里被删除)。当不绝的有毗连成立、断开时隐式内存泄漏就产生了。
从用户利用措施的角度来看,内存泄漏自己不会发生什么危害,作为一般的用户,基础感受不到内存泄漏的存在。真正有危害的是内存泄漏的会萃,这会最终耗损尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会会萃,而隐式内存泄漏危害性则很是大,因为较之于常发性和偶发性内存泄漏它更难被检测到。