C++内存泄漏检查心得

摘要:本文简单介绍了C++编程时,大家经常犯得一些内存泄漏方面的编码错误,并给出简单的代码示例。并简要给出了Win32平台下使用检测内存泄漏利器DevPartner BoundsChecker进行检查以发现泄漏代码的详细步骤。值此党的节日,希望对一些迷失在内存泄漏中的同志们有所帮助避免少走弯路。我一直觉得党的党章是完美的,原则是好的,共产主义社会肯定比资本主义财富集中在少数人手里强,只是到了下面执行就有所欠缺了,这次上海闵行封顶房的倒塌正是没有一个良好监督机制的问题,官员参股房地产明显违背政府、法律,希望祖国越来越美好。

作者博客:http://blog.csdn.net/wenhm/

闲话少说,切入正题,本人在参与一个大型Win32软件项目时,对整个项目进行了内存泄漏方面的检查,随着泄漏代码的一个个发现,发现许多的泄漏都具有某些共同性,于是乎总结了一些常见泄漏代码,发给同事们看了。希望能提醒下大家,但后来在项目的二期、三期版本出现的泄漏发现和以前的一些问题几乎是同一性质的,回过来看以前写的一些笔记发现都概括了(当时觉得自己是有那么两把刷子,^_^)。于是想到也许很多编程同志也会碰到同样的问题,能写下来放到网上起到抛砖引玉的作用就更好了。

这里总结下检测出来的内存泄漏有共性的问题,希望对大家以后编程避免内存泄漏有所帮助:

//第一种

1. 在APP开头处包含以下代码

#define CRTDBG_MAP_ALLOC

#include <stdlib.h>

#include <crtdbg.h>

2. 在APP初始化的函数中加入

_CrtDumpMemoryLeaks();

3. Output中将会跟踪所有内存创建和销毁的过程,这些信息可以忽略。

4. 程序退出时,output中将会显示出创建内存未释放的代码行信息。

这个可以解决绝大部分情况下出现的内存泄露

//第二种

在所有的CPP文件里加入以下宏定义,

用于在发生内存泄露后,可以定位到进行new操作的代码行数:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

1.  类内成员动态分配

类所有动态分配的成员变量,一定记得在析构函数中全部进行判断释放内存。当类中有指针成员变量,很多人喜欢在构造函数中来动态分配初始化指针变量但常常忘了在析构函数中来释放内存。当你初来人世,父母疼爱你,当你有了自己的小孩,你又有了生活的重心,此时父母已经被你遗忘了,当你的小孩又有了自己的小孩,你也就被遗忘了。我们很多程序员同志也是这样的,使用时我要用,new得挺爽的,用完不管我事了,这明显是违反我党一贯有始有终的原则的。

示例代码:

class CApple

{

public:

CApple()

{

m_ptrData = new char[128];

}

~CApple()

{

}

}

上面的m_ptrData指向的内存就这样泄漏掉了,记得在析构函数中加上释放的代码,改为如下:

~CApple()

{

if(NULL != m_ptrData)

{

delete m_ptrData;

m_ptrData = NULL;

}

}

需要提醒的是:上面删除m_ptrData再置NULL,是一个良好的编程习惯,可以避免产生野指针。(当然这里对象都析构了不存在这个问题,但其它很多地方将删除的指针置NULL是非常明智的一个做法,不然鬼知道这个指针指向的内存是否是有效的)

2.     指针容器

std::vector<CType>这个错误也是同志们经常犯的,其实很多时候若是简单结构、简单类,你直接用std::vector<CType>就好了,能不用std::vector<CType*>就尽量不用,因为确实很容易忘了vector中原来还存放了要释放的内存的指针,而且在clear或是删除一个元素时都得记起来释放指针指向的内容。

这个很像小时候家里收邮包,邮递员不将邮包送到家里来,也许因为太沉了吧,只是给张包裹单要自己取领。今天忙,往抽屉里一扔,然后就忘了,下次又来一包裹单,又往抽屉里一扔又忘了(指针压入vector),若你不小心将包裹单(指针)弄丢了,你自己都不知道有这么回事(忘了释放内存)。但现在就好多了,快递公司包裹直接送到你手上。

示例代码就不提供了,只能意会不能言传,^_^。

3.     指针赋值

若不是在定义指针代码作用范围内,使用其它地方定义的指针时(比如全局指针,类成员变量指针),进行赋值操作的时候先判断原来指针是否有值,有则先释放原来的内存。

因为若指针原来有值的话,你一覆盖原来分配的内存就再也找不到了,也就产生了泄漏。

代码示例:

void CMainModule::BulidList()

{

m_ptrList = new CList;

….

}

上面的代码,若BuildList跑到第二次时就会出问题了,此时m_ptrList本来就已经指向一块动态分配的内存了,你这时不分青红皂白再new一块赋值过去就将前面动态分配的内存给丢失了。

此时应该先判断m_ptrList是否为NULL,为NULL则new一块内存,否则就应考虑重用原来的内存或是先删除原来再new。

4.     扫尾函数

有些类型对象如CDialogCWindowCFileCImage等需要在Delete前做CloseReleaseDestroy等操作的,Delete时检查是否已经调用了相应的扫尾函数。

这个要具体情况具体分析了,比如CDialog的子类销毁时往往需要先调用OnDestroy或是DestroyWindow,不然就可能会存在资源泄漏的问题。

5.     公共模块/第三方库

公共模块一般有init()open()release()terminate()close()两种类型的函数,不要忘记扫尾类型函数的调用。

在我们这个软件项目中就有用到一个第三方的Av.dll,主要是进行视频编解码方面的库,这个库需要进行初始化才能用,同时也提供了使用完关闭的方法。当时一位同志就忘了调用扫尾函数导致了大量的内存泄漏。这个就要求我们使用第三方库时一定要看仔细使用说明,不要一味冒进。

6.     异常分支

若正常分支有内存需要释放,则不要忘了异常分支的内存释放如try语句的catch分支,函数中的多个return分支都要考虑到相应内存的释放。

示例代码:

try

{

void *ptrData = new char[128];

/// do something …

….

if(NULL  !=  ptrData)

{

delete ptrData;

ptrData = NULL;

}

}

catch(CException &e)

{

LOG(LOG_LEVEL_ERROR, " errorcode:" << e.errorCode());

}

catch(…)

{

LOG(LOG_LEVEL_ERROR, " errorcode:…");

}

上面的代码就没有考虑到两个异常分支也应该要判断指针是否要进行释放的情况。当跑到异常分支中去时就产生了内存泄漏了,这种问题比较难查因为正常情况下程序也是正常不会有泄漏的,能编写代码时就注意就事半功倍了。

7.     动态分配对象数组:

动态分配的对象数组,记得使用delete[]来进行删除。基于两个考虑:

(1)可以释放整个数组的空间;

(2)调用数组中每个对象的析构函数。

第一个其实使用delete加上数组地址一样是可以释放的,因为这块内存是连续分配的,不论采用delete或是delete[]来释放,操作系统都能将这块连续的内存一起释放掉。

但第二点有什么作用呢,此时大家看看 第一章类内成员动态分配 中的示例就知道了,很多释放内存的代码是放在类的析构函数中的,只有使用delete[]才能正确调用析构函数。使用delete是不会调用每个数组元素的析构函数的。

8.     非常规动态内存分配

不是采用常规内存分配(newmalloccallocrealloc)的内存也要记得释放,如strdup等。

有一些C/C++ Api返回的指针是动态分配的需要使用者来负责释放,这个只要使用时看清楚Api的说明就不会有什么问题了。

 

9.     单态模式

最好在程序退出时释放内存,虽然OS会回收,但对于我们以后内存泄漏检测工作能带来极大方便。

虽然单态模式的内存泄漏是一次性泄漏,不会导致内存的不断增加,但因为很多内存泄漏检查工具都是程序正常结束后开始统计内存泄漏的,此时会将单态模式的内存泄漏也统计进去。这样我们就得一个个区分那个是单态泄漏那个是非法泄漏,会带来很大的工作量,若能在程序退出时将单态模式的内存泄漏也释放掉,检测结果就会集中在有问题的内存泄漏上了,大大减少我们的工作量。

解决方法:

为单态模式对象定义DestroyInstance()方法用来释放单态模式的内存,在程序退出时调用该函数。

或是采用static的 smart 指针来让编译器自动在程序退出时负责释放相应的内存。

 

10.    虚析构函数

一个类的指针被向上引用,作为基类的指针来使用的时候,把析构函数写成虚函数。这样做是为了当用一个基类的指针类型来删除一个派生类的对象时,派生类的析构函数会被调用。(new子类的对象,删除时却采用delete父类类型的指针。new CConcreteClass的对象ptr,但delete CClass类型 的指针ptr,无法调用正确的析构函数)

当针对接口进行编程时,涉及到动态分配的对象指针在各函数间传递时特别要注意将基类的析构函数定义成虚函数。

第一章提到了,若没有正确的调用析构函数,析构函数中若有释放内存的代码就会得不到运行,而且本具体子类中的一些成员变量的析构函数也得不到执行。因为编译器会认为你删除的是一个基类类型的指针,当然就不会去调用子类的成员变量的析构函数的了。

代码示例:

struct ST_Info

{

int iWeight;

char strName[128]

}

class CFruit

{

};

class CApple:public CFruit

{

public:

std::vector< ST_Info> m_vecInfo;

}

CFruit * GetApple()

{

CApple *ptrApple = new CApple();

ST_Info st_Info = {9, “Apple1”};

ptrApple->m_vecInfo.push_back(st_Info);

return ptrApple;

}

void main(int argc, char**argv)

{

CFruit *ptrFruit = GetApple();

delete ptrFruit;

ptrFruit = NULL;

}

上面的代码就会产生内存泄漏了, ptrApple->m_vecInfo中存放的内存将全部泄漏掉,一个能为delete时认为这是一个CFruit *的指针,不会去释放ptrApple->m_vecInfo中元素对应的内存。

修正方法是只要将CFruit的析构函数定义成虚析构函数就OK了。

11.         线程的安全退出,user-interface thread安全退出

和窗口关联的user-interface thread 必须处理WM_DESTROY消息,建议定义一个OnDestroy()函数,该函数调用PostQuitMessage(0)的方法让user-interface thread安全退出,防止线程不安全退出导致内存泄漏。

线程进行安全退出,防止非正常退出的内存泄漏问题。

例子:

LRESULT CMsgReflect::OnDestroy(HWND hWindow, UINT uiMessage, WPARAM uiParam, LPARAM ulParam)

{

PostQuitMessage(0);

return 0;

}

12.       内存动态分配后,在各个分支路径均要考虑是否要释放掉

这个其实和第6章是类似的,下面的代码就没有考虑到执行到continue时的情况会产生内存泄漏。

for (std::vector<TeamInfo>::iterator it = e.teamlist.begin(); it != e.teamlist.end(); it++)

{

FriendGroupData *pGroup=new FriendGroupData;

if(it->unTeamID==DEFAULT_FRIEND_GROUP_ID)

continue;

….

delete pGroup;

}

附录:DevPartner BoundsChecker的使用

1).www .3ddown.com 网上可以下载到8.2版本的DevPartner,进行安装即可。

2).License的下载和安装,http://download.csdn.net/source/828960,运行Distributed License Management,将该license导入即可。

3).将系统时间改成2008年才能使用该license,此时就可以进行内存泄漏的检测了,记得检测完将系统时间改回来就OK了。

4).调整跟踪堆栈的深度,在Visual Studio界面中,DevPartner->Options,然后

Error Detection->Data Collection 即可调整跟踪堆栈的深度了。

参考资料:

1.内存泄漏的检测、定位和解决经验总结》,http://blog.csdn.net/wenhm/archive/2006/06/11/787876.aspx

时间: 2024-10-05 05:20:39

C++内存泄漏检查心得的相关文章

【转】C++内存泄漏检查心得

摘要:本文简单介绍了C++编程时,大家经常犯得一些内存泄漏方面的编码错误,并给出简单的代码示例.并简要给出了Win32平台下使用检测内存泄漏利器DevPartner BoundsChecker进行检查以发现泄漏代码的详细步骤.值此党的节日,希望对一些迷失在内存泄漏中的同志们有所帮助避免少走弯路.我一直觉得党的党章是完美的,原则是好的,共产主义社会肯定比资本主义财富集中在少数人手里强,只是到了下面执行就有所欠缺了,这次上海闵行封顶房的倒塌正是没有一个良好监督机制的问题,官员参股房地产明显违背政府.

C++程序内存泄漏检查

一.在windows平台上面:以前我都是用purify,因为没有正版的,很是麻烦. 后来我开始用windows自带的umdh,也很好用:http://support.microsoft.com/kb/268343/en-us摘要一下步骤如下: 1.准备工作: 1 gflags -i <application name> +ust 注:得先把gflags和umdh的路径加入到Path中,默认为:C:\Program Files (x86)\Windows Kits\8.1\Debuggers\x

Android —— 内存泄漏检查

今天地铁上看到一篇不错的将内存泄漏简单检查的文章,觉得还不错哟,内存泄漏确实是每个程序员头疼的事情,这里就多研究一下咯^^ 一. 常见的垃圾回收算法 参看文章 引用计数法 引用计数法基本上最简单的垃圾回收策略,它的核心思想是: 当有指针指向某实例时,计数加一, 当删除一个指针时,计数减一,当计数为0时,说明该实例没有引用可以被垃圾回收器回收. 这种回收策略的缺点是显而易见的: 1.维护引用计数是有开销的 2.计数的保存会消耗额外的空间 3.无法处理循环引用 标记清除 标记清除,顾名思义分为2步:

C/C++应用程序内存泄漏检查统计方案

一.前绪 C/C++程序给某些程序员的几大印象之一就是内存自己管理容易泄漏容易崩,笔者曾经在一个产品中使用C语言开发维护部分模块,只要产品有内存泄漏和崩溃的问题,就被甩锅“我的程序是C#开发的内存都是托管的,C++那边也没有内存(庇护其好友),肯定是C这边的问题”(话说一个十几年的程序员还停留在语言层面不觉得有点low吗),笔者毕业不到一年,听到此语心里一万头草泥马奔腾而过,默默地修改了程序,注意不是修改bug(哈哈),而是把所有malloc和free都替换成了自定义宏MALLOC和FREE,d

Linux 内存泄漏检查工具 valgrind

抄自<从零开始的JSON库教程>,先mark一下,以后再慢慢研究. ======== 引用分割线 ======== 在 Linux.OS X 下,我们可以使用 valgrind 工具(用 apt-get install valgrind. brew install valgrind).我们完全不用修改代码,只要在命令行执行: $ valgrind --leak-check=full ./leptjson_test $ valgrind --leak-check=full ./leptjson_

用mtrace检查内存泄漏

http://blog.csdn.net/ixidof/article/details/6638066内存泄漏检查方法(for Linux) 如果你更想读原始文档, 请参考glibc info的"Allocation Debugging" 一章 (执行info libc);glibc提供了一个检查内存泄漏的方法, 前提是你的程序使用glibc的标准函数 分配内存(如malloc, alloc...): 1. 在需要内存泄漏检查的代码的开始调用void mtrace(void) (在mc

内存泄漏及其检测工具

一.什么是内存泄露? 在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况.内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费. 通常我们所说的内存泄漏是指堆内存的泄漏.堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存.应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或del

(转)java内存泄漏的定位与分析

转自:http://blog.csdn.net/x_i_y_u_e/article/details/51137492 1.为什么会发生内存泄漏 java 如何检测内在泄漏呢?我们需要一些工具进行检测,并发现内存泄漏问题,不然很容易发生down机问题. 编写java程序最为方便的地方就是我们不需要管理内存的分配和释放,一切由jvm来进行处理,当java对象不再被应用时,等到堆内存不够用时,jvm会进行垃圾回收,清除这些对象占用的堆内存空间,如果对象一直被应用,jvm无法对其进行回收,创建新的对象时

基于Android Studio的内存泄漏检测与解决全攻略

自从Google在2013年发布了Android Studio后,Android Studio凭借着自己良好的内存优化,酷炫的UI主题,强大的自动补全提示以及Gradle的编译支持正逐步取代Eclipse,成为主流的Android开发IDE.Android Studio在为我们提供了良好的编码体验的同时,也提供了许多对App性能分析的工具,让开发者可以更方便分析App性能.Google在IO大会上一直告诫开发者不要无节制的使用手机内存,要注意一些不良的开发习惯会导致App的内存泄漏.虽然如今网上