Friday, April 08, 2005

VC Malloc/Free new/delete Memory Leak 防止内存泄漏

1. BSTR
BSTR bstrText = ::SysAllocString(L"Test");
SysFreeString(bstrText);

2. char*
char* lpszText2 = _com_util::ConvertBSTRToString(bstrText);
delete[] lpszText2;

lpszText2= new char[500];
free(lpszText2);
lpszText2 = NULL;//p所指向的内存释放, 但p还是指向它, the address of “p” is not changed

A. 由ConvertStringToBSTR分配的内存得手工删除
用BoundsChecker查以下语句有内存泄漏
const char* cstrBaseKeyName;
MSXML2::IXMLDOMDocument2Ptr XmlDocPtr;
MSXML2::IXMLDOMNodePtr foundNode;
...
foundNode=XmlDocPtr->selectSingleNode( _com_util::ConvertStringToBSTR(strBaseKeyName.c_str()) ); //<--Memory Leak Here
...

分析: ConvertStringToBSTR转换会分配一块内存,必须用SysFreeString删除, 修改如下后再检查就正常了
...
BSTR bstr = _com_util::ConvertStringToBSTR(strBaseKeyName.c_str()); foundNode=XmlDocPtr->selectSingleNode( bstr );
::SysFreeString(bstr);
bstr = NULL;
...

B: 由ConvertBSTRToString分配的内存得手工删除
function GetNodeValue(const char* cstrAttributeName, std::string& strAttributeValue)
{
MSXML2::IXMLDOMElementPtr elptr;
...
strAttributeValue = _com_util::ConvertBSTRToString( _bstr_t( elptr->getAttribute(_bstr_t(cstrAttributeName)) ) ); //<--Memory Leak Here
...
}

同样的问题,要修改成
...
char * szChar;
szChar= _com_util::ConvertBSTRToString( _bstr_t( elptr->getAttribute(_bstr_t(cstrAttributeName)) ) );
strAttributeValue=szChar;
delete[] szChar;
...

C. 用new 创建的对象要删除
...
ECBPara *pECBPara;
pECBPara = new ECBPara(pCtxt, dwdiid, dwdfid, dwofid, pin, filepath, fullpath);
result = MyDownload(pECBPara); //<--pECBPara用完后没有删除
...

要加上一句
delete pECBPara;


为了防止发生内存泄漏这样棘手的故障,在VC编程时应当注意遵循几个规范:
其一,如果一个类包含有指针并且分配了指针值,那么就需要构造相应的析构函数以删除该指针;
其二,如果一个函数分配了一块内存并把该内存块返回给调用它的函数使用,那么它返回的必须是一个指针而非一个引用,因为引用不能被程序删除;
其三,即使一个函数分配了一段内存并在同一函数的稍后部分删除了该内存段,也要尽可能将内存块分配到堆栈中;
最后,就是决不要试图改变一个指针值,除非已经删除指针所指的对象或通过数组指向了该指针所指向的内存,而且也不要对new返回的指针进行加1运算。

程序中的内存泄露主要有:
1 用new,malloc,GolbalAlloc等函数分配的队堆内存没有用delete,free,GlobalFree等分别对应的函数释放.这些内存在Debug时候都可以信息的,但只有new在Debug的时候回正确的指出出错的行.因为在Debug版本中Visual C++6.0对new进行了重定义,建议大家分配内存时尽量使用new函数.
2 在绘图时创建的GDI对象没有释放,但如果是使用MFC的GDI对象类,出错的几率会小很多.用new创建的对象会被检测到,而如果使用局部变量,在局部变量失效后自动释放,不会造成内存泄露.
3 创建的线程,窗口等句柄的资源,在失效后没有释放掉.

使用Performance Monitor检测内存泄漏
NT的内核在设计过程中已经加入了系统监视功能,比如CPU的使用率,内存的使用情况,I/O操作的频繁度等都作为一个个Counter,应用程序可以通过读取这些Counter了解整个系统的或者某个进程的运行状况。Performance Monitor就是这样一个应用程序。
为了检测内存泄漏,我们一般可以监视Process对象的Handle Count,Virutal Bytes 和Working Set三个Counter。Handle Count记录了进程当前打开的HANDLE的个数,监视这个Counter有助于我们发现程序是否有Handle泄漏;Virtual Bytes记录了该进程当前在虚地址空间上使用的虚拟内存的大小,NT的内存分配采用了两步走的方法,首先,在虚地址空间上保留一段空间,这时操作系统并没有分配物理内存,只是保留了一段地址。然后,再提交这段空间,这时操作系统才会分配物理内存。所以,Virtual Bytes一般总大于程序的Working Set。监视Virutal Bytes可以帮助我们发现一些系统底层的问题; Working Set记录了操作系统为进程已提交的内存的总量,这个值和程序申请的内存总量存在密切的关系,如果程序存在内存的泄漏这个值会持续增加,但是Virtual Bytes却是跳跃式增加的。
监视这些Counter可以让我们了解进程使用内存的情况,如果发生了泄漏,即使是隐式内存泄漏,这些Counter的值也会持续增加。但是,我们知道有问题却不知道哪里有问题,所以一般使用Performance Monitor来验证是否有内存泄漏,而使用BoundsChecker来找到和解决问题。