C++的内存管理是很重要的课题。其中的内存泄漏,又是困扰开发人员的噩梦。笔者根据自己工作中的经验,总结了一些宝贵经验,分享于此。
一、若干导致内存泄漏的原因
(1)没有匹配地调用new和delete。
(2)没有正确调用匹配的new[]和delete[]。
对于语言内置的基本类型,delete 和 delete[] 是等价的。
但是对于用户自定义类型,不能用delete 代替 delete[] 。
(3)警惕要在构造函数和析构函数中匹配地调用new和delete。
在一个对象A中以指针的形式使用其他对象B,这属于指针嵌套的一种,那么在对象A的析构函数中要将B也销毁掉。
但如果A是以对象的形式使用B,那么B的析构函数会自动被调用。
(4)要区分清楚,指向对象的指针数组,不等同于对象数组。分配:Point **p = new Point*[10];
for(int i=0;i<10;i++)
p[i] = new Point;销毁: for(int i=0;i<10;i++)
delete p[i];
delete p;//等同于delete []p;
(5)缺少自定义的copy-construct,深拷贝-浅拷贝不当,造成内存泄漏。
除了内存泄漏,还可能引起指针的二次释放。
这一条对于拥有资源的类来讲,尤其要注意。
(6)缺少自定义的operator=,深拷贝-浅拷贝不当,造成内存泄漏。
(7)未将基类的析构函数定义为虚函数。
这会导致派生类的析构函数不被调用。
(8)不要以引用的形式返回函数内部的局部对象。
在C++98/03中,这一点的确是一个问题。一种处理手法是使用常量左值引用(const-left-reference)来解决该问题。
在C++11中,增加了右值引用、move语义、完美转发forward,就可以很轻松地解决第(8)条所提出的问题。
二、平台依赖的内存泄漏的检查
不管是windows平台还是linux平台,检查内存泄漏的思路都是一样的,原理大致如下:内存分配要通过CRT在运行时实现,只要在分配内存和释放内存时分别做好记录,程序结束时对比分配内存和释放内存的记录就可以确定是不是有内存泄漏。
(1)windows平台:主要是_CRTDBG_MAP_ALLOC宏和_CrtDumpMemoryLeaks()函数。
(2)linux平台:主要是借助于valgrind工具。
在这里笔者想说的是,借助于工具是有一定局限的。首先,就是工具函数是在程序执行完毕之后调用,一般地我们写程序都会带有内存申请失败校验,无可用内存时软件不一定会要退出。其次,对于大量、频繁申请、释放内存的软件,形成的转储调试信息会非常多,可能还会存在地址重复,导致分析难度很大。
三、避免内存泄漏代码被提交的几点建议
所以,就笔者的开发经验看,如下的几点建议可以在前期有效避免一些潜在的内存管理问题:
(建议1)形成专家组对代码进行审议。包括事前需坚持去做的、代码提交前的审核工作,发生内存泄漏时的代码阅读分析工作。
(建议2)养成良好的编程习惯。时刻在心里警惕:凡new必delete。
(建议3)高级主题:使用内存池做内存管理。
(建议4)高级主题:使用智能指针。