在windows下开发C++程序的时候,我们经常需要用到malloc开申请内存,然后利用free回收内存,但是开发人员的不小心可能会忘记free掉内存,这样就导致了内存泄露
1.利用库检测内存泄露信息
#define _CRTDBG_MAP_ALLOC //如果没有这个宏定义,我们只能知道有内存泄露,却无法知道在哪个地方申请内存忘记了释放 #include <stdlib.h> #include <crtdbg.h> int main(void) { char *p = (char *)malloc(sizeof(char) * 100); _CrtDumpMemoryLeaks(); }
使用crtdbg来检测到内存泄露很简单,只要在文件的第一行定义_CRTDBG_MAP_ALLOC,然后include头文件crtdbg.h,在程序需要内存检测的地方调用_CrtDumpMemoryLeaks,就可以输出内存泄露的信息,如上面的程序,我们申请了100个字节的内存而没有释放,但是我们可以很清楚地看到内存泄露在 哪个地方,如下图
我们在main.cpp这个文件中的第八行申请了内存,但是没有进行释放
那么编译器是怎么知道我们有内存泄露呢??就是利用宏定义把我们的调用的malloc替换成crtdbg 库里面的_malloc_dbg,我们在申请内存的时候,_malloc_dbg会先记录下我们申请内存的行数以及大小(记得编译器有内置的宏定义__LINE__和__FILE__不?),把这些信息放到一个list(只是举例,使用list保存这些信息一旦程序大了会很慢)里面,当我们free内存的时候,把这块内存的信息从list里面删除掉,我们调用_CrtDumpMemoryLeaks()的时候就是在把这个list的信息依次打印出来而已
下面是我们定义_CRTDBG_MAP_ALLOC后实际上所调用的malloc原型,malloc已经成了一个宏定义
#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
当然,我们一般调用_CrtDumpMemoryLeaks的时候都是在程序的结尾处,如果我们的程序有多个出口,我们值需要在程序开始处调用_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ) 就可以了
有时候我们需要检测某一段代码是否有内存泄露,crtdbg库也可以帮我们做到
_CrtMemState s1; _CrtMemState s2; _CrtMemCheckpoint(&s1); char *p2 = (char *)malloc(400); _CrtMemCheckpoint(&s2); _CrtMemState s3; if (_CrtMemDifference(&s3,&s1,&s2)) { _CrtMemDumpStatistics(&s3); }
这样,我们在输出窗口将会看到s1和s2之间的内存使用信息:
0 bytes in 0 Free Blocks.
400 bytes in 1 Normal Blocks.
0 bytes in 0 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 0 bytes.
Total allocations: 400 bytes.
crtdbg库也有缺点,当你使用一个别人提供的lib或者dll库的时候,你调用这个函数,这个函数分配了内存,需要你去调用另外一个函数才能把内存释放掉,但是你不知道这个函数需要调用另外一个函数才能释放掉内存,这个是无法通过crtdbg库检测出来的,这个函数包括c++的new函数,所以这个库实际上不适用C++
2.利用share_ptr来管理内存
如果有使用过boost库的应该知道,boost里面有一个shart_ptr被誉为神器,因为他可以帮我们自动管理内存,具体用法很简单:
boost::shared_ptr < connection > p ( new connection());
这样的话我们不需要去delete内存,shartd_ptr会在我们不需要这快内存的时候帮我们delete掉,shartd_ptr内部是使用引用计数以及C++的RAII,有别的对象引用该指针的时候引用技术就+1,shartd_ptr析构函数调用的时候引用计数就-1,当为0的时候就delete掉该指针,所以我们并不需要调用delete来释放资源,share_ptr会帮我们管理
shared_ptr虽然看起来很好用,但是当程序一旦复杂起来,shared_ptr依然也会变复杂(shared_ptr四宗罪),当然boost本身就比较复杂,这个也是我比较不喜欢boost的一个原因
3.将资源集中管理
这个也是我比较经常使用的方法,特别是在大程序的使用,配合单件模式,将资源在整个程序或者模块中集中管理,这样在程序结束的时候只要我们在析构函数里面有清理这些资源,我们就可以避免内存泄露,对于数据的一些写操作全部在这个类中统一操作,如果要暴露内部的数据,只对外提供const数据(可以通过强转去掉const属性)
当然这个方法并不适用于所有场景,比如我们需要提供库给别人,我们没办法预测到客户需要什么操作,所以这个方法只适用内部团队开发
总之内存管理据我所知到现在还是没有什么好的解决方法,特别是当代码一旦膨胀的时候,到现在好像java,python,erlang都有内存泄露的问题,我们只能在平常开发中多注意了
参考资料:
陈硕的博客(有一些shared_ptr的资料,也可以从这里看出shared_ptr使用起来没那么简单)