C++内存泄露和检测

C++中的内存泄露通常指堆中的内存泄露。堆内存是咱们手动malloc/realloc/new申请的,程序不会自动回收,须要调用free或delete手动释放,不然就会形成内存泄露。内存泄露其实还应该包括系统资料的泄露,好比socket链接等,使用完后也要释放。ios

内存泄露的缘由:c++

总结下来,内存泄露大概有一下几个缘由:web

一、编码错误:malloc、realloc、new申请的内存在堆上,须要手动显示释放,调用free或delete。申请和释放必须成对出现malloc/realloc对应free,new对应delete。前者不会运行构造/析构函数,后者会。对于C++内置数据类型可能没差异,可是对于本身构造的类,可能在析构函数中释放系统资源或释放内存,因此要对应使用。缓存

二、“无主”内存:申请内存后,指针指向内存的起始地址,若丢失或修改这个指针,那么申请的内存将丢失且没有释放。服务器

三、异常分支致使资源未释放:程序正常执行没有问题,可是若是遇到异常,正常执行的顺序或分支会被打断,得不到执行。因此在异常处理的代码中,要确保系统资源的释放。socket

四、隐式内存泄露:程序运行中不断申请内存,可是直到程序结束才释放。有些服务器会申请大量内存做为缓存,或申请大量Socket资源做为链接池,这些资源一直占用直到程序退出。服务器运行起来通常持续几个月,不及时释放可能会致使内存耗尽。函数

五、类的析构函数为非虚函数:析构函数为虚函数,利用多态来调用指针指向对象的析构函数,而不是基类的析构函数。编码


内存泄露的检测spa

内存泄露的关键就是记录分配的内存和释放内存的操做,看看能不能匹配。跟踪每一块内存的声明周期,例如:每当申请一块内存后,把指向它的指针加入到List中,当释放时,再把对应的指针从List中删除,到程序最后检查List就能够知道有没有内存泄露了。Window平台下的Visual Studio调试器和C运行时(CRT)就是用这个原理来检测内存泄露。指针

在VS中使用时,需加上

#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>

crtdbg.h的做用是将malloc和free函数映射到它们的调试版本_malloc_dbg和_free_dbg,这两个函数将跟踪内存分配和释放(在Debug版本中有效)

_CrtDumpMemoryLeaks();

函数将显示当前内存泄露,也就是说程序运行到此行代码时的内存泄露,全部未销毁的对象都会报出内存泄露,所以要让这个函数尽可能放到最后。

例如:

  1. #define _CRTDBG_MAP_ALLOC  

  2. #include <crtdbg.h>  

  3. #include <iostream>  

  4. using namespace std;  

  5. int main(int argc,char** argv)  

  6. {  

  7.     char *str1 = NULL;  

  8.     char *str2 = NULL;  

  9.     str1=new char[100];  

  10.     str2=new char[50];  

  11.   

  12.     delete str1;  

  13.     _CrtDumpMemoryLeaks();  

  14.     return 0;  

  15. }  


上述代码中,内存申请了两块,可是只释放了一块,运行调试,会在output窗口输出:

Dumping objects ->
{136} normal block at 0x00084D70, 50 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

能够看到会检测到内存泄露。 可是并无检测到泄露内存申请的位置,已经加了宏定义#define _CRTDBG_MAP_ALLOC。缘由是申请内存用的是new,而刚刚包含头文件和加宏定义是重载了malloc函数,并无重载new操做符,因此要本身定义重载new操做符才能检测到泄露内存的申请位置。修改以下:

  1. #define _CRTDBG_MAP_ALLOC  

  2. #include <crtdbg.h>  

  3. #ifdef _DEBUG //重载new  

  4. #define new  new(_NORMAL_BLOCK, __FILE__, __LINE__)    

  5. #endif  

  6. #include <iostream>  

  7. using namespace std;  

  8. int main(int argc,char** argv)  

  9. {  

  10.     char *str1 = NULL;  

  11.     char *str2 = NULL;  

  12.     str1=(char*)malloc(100);  

  13.     str2=new char[50];  

  14.   

  15.     _CrtDumpMemoryLeaks();  

  16.     return 0;  

  17. }  

运行结果:

Detected memory leaks!
Dumping objects ->
e:\c++\test\内存泄露检测2\main.cpp(13) : {62} normal block at 0x001714F8, 50 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
e:\c++\test\内存泄露检测2\main.cpp(12) : {61} normal block at 0x00171458, 100 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

能够看到

main.cpp()括号里面的数字就是泄露内存的起始位置。那么后面的{62} normal block at 0x001714F8, 50 bytes long.
表明什么?

大括号{}里面的数字表示第几回申请内存操做;0x001714F8表示泄露内存的起始地址,CD CD表示泄露内存的内容。

为何是第62次申请内存,由于在初始化操做时也申请了内存。经过这个信息,能够设置断点。调用long _CrtSetBreakAlloc(long nAllocID)能够再第nAllocID次申请内存是中断,在中断时获取的信息比在程序终止时获取的信息要多,你能够调试,查看变量状态,对函数调用调试分析,解决内存泄露。

block分为3中类型,此处为normal,表示普通,此外还有client表示客户端(专门用于MFC),CRT表示运行时(有CRT库来管理,通常不会泄露),free表示已经释放掉的块,igore表示要忽略的块。

在上面程序中,调用_CrtDumpMemoryLeaks()来检测内存泄露,若是程序可能在多个地方终止,必须在多个地方调用这个函数,这样比较麻烦,能够在程序起始位置调用_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ),这样不管程序什么时候终止,都会在终止前调用_CrtDumpMemoryLeaks()。

 

除此以外,还能够在某时刻设置检查点,获取当时内存状态的快照。比较不一样时刻内存状态的差别。

  1. #define _CRTDBG_MAP_ALLOC  

  2. #include <crtdbg.h>  

  3. #ifdef _DEBUG //重载new  

  4. #define new  new(_NORMAL_BLOCK, __FILE__, __LINE__)    

  5. #endif  

  6. #include <iostream>  

  7. using namespace std;  

  8. int main(int argc,char** argv)  

  9. {  

  10.     _CrtMemState s1, s2, s3;  

  11.     char *str1 = NULL;  

  12.     char *str2 = NULL;  

  13.     str1=(char*)malloc(100);  

  14.     _CrtMemCheckpoint( &s1 );//记录内存快照  

  15.     _CrtMemDumpStatistics( &s1 );//输出  

  16.     str2=new char[50];  

  17.     _CrtMemCheckpoint( &s2 );  

  18.     _CrtMemDumpStatistics( &s2 );  

  19.   

  20.     if ( _CrtMemDifference( &s3, &s1, &s2) )//比较s1和s2,把比较结果输出到s3  

  21.         _CrtMemDumpStatistics( &s3 );// dump 差别结果  

  22.   

  23.     return 0;  

  24. }  


输出结果为:

0 bytes in 0 Free Blocks.
100 bytes in 1 Normal Blocks.
8434 bytes in 54 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 8963 bytes.
Total allocations: 14003 bytes.
0 bytes in 0 Free Blocks.
150 bytes in 2 Normal Blocks.
8434 bytes in 54 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 8963 bytes.
Total allocations: 14053 bytes.
0 bytes in 0 Free Blocks.
50 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: 50 bytes.


也能够用此法更复杂检测内存泄露,例如设置检查点,检查检查点之间的内存泄露。

在Linux下也有相似的方法,具体能够参考:http://en.wikipedia.org/wiki/Mtrace

相关文章
相关标签/搜索