MyMSDN

MyMSDN记录开发新知道

关于内存泄露的问题(解决+剖析)

首先先阐明这篇随笔的意图,只在告诉读者,内存泄露的神不知鬼不觉,希望能引起大家的注意。
一段代码的意思如何正确表达,才能不造成内存泄露呢?很多朋友经常泄露了内存但却查找不到原因。当然在CLI/C++中利用托管对象堆上的垃圾收集器是可以更好地避免这一点。但是在更早的版本中,程序员有必要去手动删除这些相关资源。否则将在程序关闭的时候出现一些错误。
MFC
现在我们去重载一个虚函数virtualvoidDeleteContents();用来在销毁文档数据前调用框架删除一些文档类的数据,(MSDN:Called by the framework to delete the document's data without destroying the CDocument object itself.)
先批评一段代码:

1 void  CGraphicDoc::DeleteContents() 
2 {
3      for ( int  i = 0 ;i < m_obArray.GetSize();i ++ )
4      {
5         ……
6     }

7     CDocument::DeleteContents();
8 }
评价:这段代码看似简练,但是却很浪费资源,在第3行的for循环中,i<m_obArray.GetSize();当每次进行判断的时候将再次调用GetSize(),如果这个数据的量是个天文数字,那么这样的调用无疑是一种灾难。

优化:
 1void CGraphicDoc::DeleteContents() 
 2{
 3    int nCount;
 4    nCount=m_obArray.GetSize();
 5    for(int i=0;i<nCount;i++)
 6    {
 7        ……
 8    }

 9    CDocument::DeleteContents();
10}

填写for循环内的语句。这里的任务:删除之前利用CObArray : m_obArray对象保存的一个指针所指向的对象,以及指针本身。因此(以下提供几种常见的错误代码)
代码A:
 1void CGraphicDoc::DeleteContents() 
 2{
 3    int nCount;
 4    nCount=m_obArray.GetSize();
 5    for(int i=0;i<nCount;i++)
 6    {
 7        delete m_obArray.GetAt(i);   //删除对象指针所指向的对象
 8        m_obArray.RemoveAt(i);   //删除对应的指针本身
 9    }

10    CDocument::DeleteContents();
11}
代码B:
 1void CGraphicDoc::DeleteContents() 
 2{
 3    int nCount;
 4    nCount=m_obArray.GetSize();
 5    for(int i=0;i<nCount;i++)
 6    {
 7        delete m_obArray.GetAt(i);
 8        m_obArray.RemoveAt(0);
 9    }

10    CDocument::DeleteContents();
11}
代码A看起来似乎很符合常规思维,因此也很容易迷惑人。但是在程序运行的时候出现了错误。但是在MSDN中查找CObArray Class Members,查看RemoveAt,其中remarks中有这样一句:In the process, it shifts down all the elements above the removed element(s). It decrements the upper bound of the array but does not free memory. 它的意思就是假设你一共有3个数据 p[0]=a、p[1]=b、p[2]=c,当你删除第0个数据之后,数据将整体向前移动,变为p[0]=b、p[1]=c。这样当你i=nCount-1的时候就根本没有这样的数让你删除,因为假设有那个时刻的话,数据的元素只有1个,而他的编号是0而不是nCount.因此出现了无法删除的现象。因此也就隐含了问题了。
因此有人突发奇想:如果我每次只删除第0个数据的话,那么是否就可以了呢?于是代码B诞生了。可是问题终究没能得到解决。因为假设有一组数据一共3个。删除了编号0的元素(delete语句),移除了该元素的指针,此时i=1,进入删除,又到了delete语句,这时候删除元素i=1这样的语句,这时实际上是删除了先前元素中的第二个元素,而不是第一个。而0与2中间的第1个元素则未被删除。又出现了隐含问题。其实只要将两个都改为0,每次都删除第一个就可以了。
 1void CGraphicDoc::DeleteContents() 
 2{
 3    int nCount;
 4    nCount=m_obArray.GetSize();
 5    for(int i=0;i<nCount;i++)
 6    {
 7        delete m_obArray.GetAt(0);
 8        m_obArray.RemoveAt(0);
 9    }

10    CDocument::DeleteContents();
11}
另外可以将程序倒写过来,避免RemoveAt对其进行重新整合队列做产生的不可预料的麻烦。
 1void CGraphicDoc::DeleteContents() 
 2{
 3    int nCount;
 4    nCount=m_obArray.GetSize();
 5    while(nCount--)
 6    {
 7        delete m_obArray.GetAt(nCount);
 8        m_obArray.RemoveAt(nCount);
 9    }

10    CDocument::DeleteContents();
11}

12
或者仔细察看MSDN中还有一个函数叫RemoveAll()它是用来删除整个CObArray集合对象的。
 1void CGraphicDoc::DeleteContents() 
 2{
 3    int nCount;
 4    nCount=m_obArray.GetSize();
 5    for(int i=0;i<nCount;i++)
 6    {
 7        delete m_obArray.GetAt(i);
 8    }

 9    m_obArray.RemoveAll();
10    CDocument::DeleteContents();
11}

posted on 2006-08-16 03:44 volnet 阅读(4172) 评论(5)  编辑 收藏 引用

评论

# re: 关于内存泄露的问题(解决+剖析) 2006-08-16 08:54 szwolf

写得非常好: )
对MFC内部的细节了解得很详细,C++内存管理思路也良清淅。  回复  更多评论   

# re: 关于内存泄露的问题(解决+剖析) 2006-08-16 09:15 小明

另外一种我经常用的写法
for(int i=0,nCount = m_obArray.GetSize();i<nCount;++i)
{
delete m_obArray.GetAt(i);
}

另外我不建议在循环中RemoveAt,效率太差。  回复  更多评论   

# re: 关于内存泄露的问题(解决+剖析) 2006-08-16 09:16 mzty

en ,不错的啊  回复  更多评论   

# re: 关于内存泄露的问题(解决+剖析) 2006-08-16 10:08 volnet

呵呵,谢谢大家,你们的评论让我更有信心多写好文章
发文章最怕没人看了。和存档案一样,还不如存自家硬盘呢。。。。
所以看文章,+评论,大家一起互动~  回复  更多评论   

# re: 关于内存泄露的问题(解决+剖析) 2009-07-18 14:31 cs

写技术博客很辛苦的哦,谢谢你了  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


特殊功能