再次抱怨下:编写C++程序,最烦人的事情就是对象的内存管理工作。好不容易把内存访问冲突问题搞定,又跑出内存泄漏一渣。
幸运的是,C++的IDE会帮我们找出程序的内存泄漏。不幸的是,IDE老是在程序退出调试时提醒我们找到内存泄漏,无形中给我们极大的压力。苦笑一下。
看了很多资料(包括MSDN),都说要添加下面的语句,在调试时才能输出内存泄漏信息:
事实上,在VS2005上,不用对项目做任何额外的设置,也不用添加上面提到的代码,在程序调试的退出时,默认即会在输出框输出内存泄漏信息。如:
VS能检查的内存泄漏有两类,第一类是知道内存分配代码的位置,双击即可定位;第二类不能定位代码,只给出内存分配块号(编号),如上面的“{9878}”
如果根据块号找到对应的内存分配(new)代码呢?可以使用一个C运行时环境变量(_crtBreakAlloc),在内存分配编号上设置断点。即调试项目(F5),在系统分配指定块号的内存时,调试器将触发一个断点,位置在分配代码上。此时可以跟踪其堆栈信息,分析内存泄漏的原因。
内存分配编号上设置断点的方法是:
1. 根据调试信息得到某一泄漏内存块的编号。
2. 在程序启动时(确保分配指定内存块之前),给_crtBreakAlloc赋值为内存编号,如:_crtBreakAlloc=9878;
3. 再次调试运行,程序将在指定的内存块分配时中断。
注意:并不是所有泄漏块号都能用这种方法定位。只有在内存块号固定时(每次调试,泄漏块号都相同),这种方法才有效。
OK,找到内存泄漏位置之后,接下来就是找出泄漏的原因,并解决它!
这里列出几种常见的引起内存泄漏的场合,以供参考:(按泄漏对象的可视范围划分)
1. 函数栈级对象:在函数内,暂时申请了一块内存(没有赋给类成员指针或全局指针,如临时缓冲),但没有释放它。这种情况最简单,
容易解决,在用完后释放即可。
2. 类级规则对象:在构造函数中分配,在普通成员函数中访问,但在析构函数中忘记释放。这种情况也简单,析构函数中释放它即可。如果在类编写时就注意到这点,那么这类泄漏一般可以避免。
3. 类级非规则对象:在构造函数中没有分配,只是简单初始化为NULL;对象分配推迟到普通的成员函数(可能是节省内存考虑);在析构函数中安全释放对象。所谓安全释放,是这样一种调用形式:判断指针是否为空,如果非空,则释放它,然后复位指针为空,即COM中的宏SAFE_DELETE(p)。可以看出,这种对象管理形式一般能很好地工作,但中间环节不能有任何遗漏。忽略了一环,就可能出现泄漏。这里重点说一下对象分配所在的普通成员函数,暂时称之为对象分配函数吧。这个函数可能被客户代码调用多次,因此在为对象分配内存前,请先安全释放它;或者使用机制确保分配代码只会被执行一次,怎么解决取决于类的设计思想。
4. 类外级包络对象:在分析泄漏时,如果排除了类内可能出现的泄漏(通常是类设计缺陷),但目标泄漏仍然出现,是时候检查该类的客户代码了。很有可能,组合了泄漏对象的对象本身也没有释放,而且可以继续往上回朔。