昨天晚上梦见项目中解码部分又出现了内存泄露,今天到公司,小曲果然跟我说LogFileReader中存在内存泄露问题(不过最后的结果是发生泄露的地方和梦里的不一样~:P)。
内存泄露这个问题说大不大,可也不小。要是有那么点点泄露,你的程序又不要求长期稳定运行,那也没太大关系。不过我一向是以能够长期稳定运行来要求自己的程序的,所以就不得不干掉它,不然迟早要被它把内存给吃干净了。
VC6的环境中有一个好处,就是会有内存泄露的提示。 形如:
Detected memory leaks!和Dumping objects ->我就不解释了,不懂自己查词典去。
后面的每一处内存泄露的格式都是
{分配申请号} 块类型 at 首地址,大小 bytes long.
Data:< ASCII码显示的前16字节内容 > 十六进制显示的前16字节内容
幸运的话,dbg信息中还有关于申请分配动作所在的文件和行号的纪录,那就会在上面的内容之前显示出来。那样的话解决问题就容易多了。问题是如果你像我上面举的例子那样,没有给出文件和行号,那你可就要费点功夫咯。
我再来介绍一个技巧,上面讲过内存分配申请号和首地址两个很有用的信息,这两个可以用来帮助你设置断点。用地址为条件设置断点,意图在于当分配了一块内存,其首地址刚好为我们设置的条件中的首地址的时候就中断,我们接着跟踪来探查是否存在内存泄露(因为分配、释放的缘故,同一个地址可能会被使用很多次,所以要往下跟踪探查),而以非配申请号为中断条件用意在于发生一个内存分配,请求编号为我们设置的值时,就中断。不过这两招也会失灵,等会再说为什么。
以申请号为中断条件,你需要在程序启动的地方加入一行代码
_crtBreakAlloc = xx;
或者是
_CrtSetBreakAlloc(xx);
其中的xx就是你要设置的申请号了,至于会如何中断,就留给看官自己去试验了。
以地址为条件呢,就繁琐一点,你要先定位到你程序中用于分配的那个函数,比如VC6的MFC中debug的new就在X:\Program Files\Microsoft Visual Studio\VC98\MFC\SRC\AFXMEM.cpp中,找到函数的最后一个}设置条件断点,条件是pResult == 首地址值,怎么设置断点的条件?不用我教了吧,不会就自己去网上查吧。用这个办法还能够设置大小的条件中断。不过呢,有个问题要注意,以new来说,不同的预编译条件会导致它有几条不同路线,其他的分配方式当然也是有的,留着看官自己探索吧。
下面解释一下这些方法为什么会失效,原因其实很简单,因为每次运行的时候这个申请编号和首地址都几乎是变化的,所以你只有尝试,如果这些值没有变化,那就恭喜你,可能会发现问题了,要是变化了,可能你也就是竹篮打水一场空了。而如果是通过大小来设置条件断点,你可能就能够抓住问题,因为发生泄露的内存大小一般都是不变的,而万一这个大小太普通了,跟踪100次才能找到问题所在也是可能的。:P
最后,再介绍点有用的,你有没有发现MFC给你生成的cpp文件里面都有这么几行:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
这几行有什么用呢?如果发生内存泄露的文件中有这几行,那么,恭喜你,Output里面就会有文件名和行号了~至于为什么,呵呵,还是老话,留给看官研究吧。
基本上排查内存泄露的方法技巧,我已经知无不言了。可今天咱遇到的问题,却没那么好找,这工程里面基本上代码都是别人写的,而且文件还不少,前几天帮忙她改的解码文件就有差不多10万行的代码量,而且她那个解码库里面还用的是自己的内存分配管理,上次就是查那个解码库中的内存泄露整了我半天时间,昨天晚上那梦不是没理由的,我就担心这里面出问题。不过还好,今天这问题不在这里面。
追查的过程我就不描述了,最后的原因倒是值得一提,为什么呢?因为虚拟析构函数的问题。NND!今天把老工程里面的一些代码翻出来看,才发现问题大大的有,修改了好几个类和内存泄露相关的问题,相信有些是目前没有暴露出来的,可那提示依然存在,我当时也稀里糊涂的,我在子类的析构函数中释放的地方设置的断点从来没有断下来过,我居然没怀疑这个事情,唉,现在想起来就惭愧。最后我才注意到这个问题,前面搞什么条件中断阿,什么分配申请号中断阿,全在浪费力气。最后注意到的时候,都已经流了不少汗了,这下想到析构函数的virtual问题,子类的析构函数倒是带了“virtual”关键字,找到基类一看,NND,没有构造函数声明,没有析构函数声明,全部是默认的,难怪进不去。果然声明一个空的虚拟析构函数,问题就解决了,提示没有了~555555早知道先把工程的每个cpp文件中头上加上那么一段,让它给我定位出来再查问题了~