原文链接:https://blog.csdn.net/psbeond/article/details/99546363

日期: 2016-12-20

参考:MSDN:ms-help://MS.MSDNQTR.v90.chs/dv_vsdebugnative/html/cf6dc7a6-cd12-4283-b1b6-ea53915f7ed1.htm

通过在MSDN中输入:DEBUG_NEW,可以找到“DEBUG_NEW 宏”,在"请参见其他资源"中,打开“MFC中的内存泄漏检测”,在“MFC中的内存泄漏检测”页面下方的相关章节中,可找到“检测和隔离内存泄漏”,它就是介绍如何使用 C 运行时库检测内存泄漏,链接就是上面那个链接。

动态分配和释放内存的功能是 C/C++ 编程的最强大功能之一,但最大的长处也可能成为最大的弱点。C/C++ 应用程序即是如此,在这些应用程序中,内存处理问题属于最常见的 bug。
幸运的是,Visual Studio 调试器和 C 运行时 (CRT) 库为您提供了检测和识别内存泄漏的有效方法。
这些方法不仅MFC程序可以使用,win32程序也可以使用。并且这些方法,不仅C++的内存分配方式(如new)可用,C的内存分配方式(如malloc)也可用。

零、原理
在VC中编写C/C++程序时,我们对new、malloc等的调用,在Debug模式下,最终都会调用_heap_alloc_dbg_impl。_heap_alloc_dbg_impl内部会真正分配内存,并且记录内存分配的文件名、行号、需要分配的内存大小及本次内存分配是整个程序第几次分配(在MSDN中叫“内存分配编号”,每调用_heap_alloc_dbg_impl一次,这个值加1,这个值在某些情况下可用来调试,非常重要。我们在程序入口处,调用:
_CrtSetBreakAlloc(111);
那么在第111次分配内存时,程序就会中断。
参考:ms-help://MS.MSDNQTR.v90.chs/dv_vsdebugnative/html/5d80a876-4540-4a2f-b935-e40fdfff4263.htm)。_heap_alloc_dbg_impl内部把这个记录保存在一个名为_CrtMemBlockHeader的结构体节点中,然后再把_CrtMemBlockHeader节点加入到双向链表_pFirstBlock中,_pFirstBlock是类型为_CrtMemBlockHeader的全局变量,定义为:
static _CrtMemBlockHeader * _pFirstBlock;

虽然我们不做任何设置,VC就可以检测内存泄漏,但通常情况下,由于在调用new、malloc分配内存时,并没有把分配内存的文件名及行号传递给_heap_alloc_dbg_impl,所以检测到的内存泄漏并不能准确定位,对于实际开发,意义不大。

本文重点要讨论的就是如何让new、malloc等内存分配方式,可以传文件名及行号给_heap_alloc_dbg_impl。

对于C++语言的分配方式,原理是通过重载new操作符,让new执行到带文件名和行号参数的operator new函数上(注意这里是函数)。
对于C语言的分配方式,原理是通过类似下面的宏定义,
#define   malloc(s)             _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
把malloc(s),映射到对_malloc_dbg函数的调用。

一、对于Win32程序。
1.1 对于C分配的内存(本小节的内容是通用方法,MFC也是通过封装本小节的内容实现的)
Win32对C语言分配的内存进行泄漏检测是通过<crtdbg.h>文件中,对malloc等函数的重定义实现的。

在<crtdbg.h>文件中,有如下宏定义:
#ifdef  _CRTDBG_MAP_ALLOC    // 注意这个条件宏
#define   malloc(s)             _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
// 其它C内存分配函数的宏定义,如calloc等
#endif  /* _CRTDBG_MAP_ALLOC */

通过包括 crtdbg.h,将 malloc 和 free 函数映射到其“Debug”版本 _malloc_dbg 和 _free_dbg,这些函数将跟踪内存分配和释放。此映射只在调试版本(在其中定义了 _DEBUG)中发生。发布版本使用普通的 malloc 和 free 函数。
_malloc_dbg接收文件名、行号参数。_malloc_dbg最终会调用到_heap_alloc_dbg_impl,并把分配内存的文件名、行号传递给_heap_alloc_dbg_impl。
所以,要想检测C语言分配的内存泄漏,就要包含头文件<crtdbg.h>,并且在包含头文件前,定义宏_CRTDBG_MAP_ALLOC。
并非绝对需要该宏定义,但如果没有该宏定义,内存泄漏转储包含的有用信息将较少。这是因为当没有包含这个宏时,malloc函数只接收size_t nSize参数,不再包含文件名和行号。

MSDN文章:ms-help://MS.MSDNQTR.v90.chs/dv_vsdebugnative/html/43eedaa1-3f26-463c-9c31-ae21f10ed16d.htm推荐的头文件如下:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

注:#include 语句必须采用上文所示顺序。如果更改了顺序,所使用的函数可能无法正确工作。
到这里,对malloc等的调用,都会被记录下来,但运行一遍程序,会发现,并没有打印任何泄漏信息。这是因为目前为止,只记录了内存分配,并没有输出信息。
输出记录的未释放内存,是通过调用_CrtDumpMemoryLeaks();实现。_CrtDumpMemoryLeaks的作用就是收集所有未释放的内存信息,并打印出来。所以,通常_CrtDumpMemoryLeaks要放到程序结束的位置。但一个程序往往有多个结束的位置,并且,_CrtDumpMemoryLeaks打印的是执行_CrtDumpMemoryLeaks的时候,未释放的内存。有些内存,往往在_CrtDumpMemoryLeaks之后释放,_CrtDumpMemoryLeaks仍然会报告出泄漏。
解决方法是:在程序开始处调用:
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
该语句在程序退出时自动调用 _CrtDumpMemoryLeaks,我们不需要再调用_CrtDumpMemoryLeaks打印报告了。

进阶篇:
在_CrtDumpMemoryLeaks中加断点发现,当调用了_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF)之后,vc程序会在__DllMainCRTStartup函数中处理(dwReason == DLL_PROCESS_DETACH)分支时,调用_CRT_INIT,而_CRT_INIT内部,调用了_CrtDumpMemoryLeaks。

1.2 对于C++分配的内存
#ifdef _DEBUG
#define new   new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

通过宏定义,把对new的调用,映射到带文件名和行号参数的operator new函数上,并最终调用到_heap_alloc_dbg_impl,并把分配内存的文件名、行号传递给_heap_alloc_dbg_impl。

注:_NORMAL_BLOCK也是在#include <crtdbg.h>中定义。并且C++分配的内存,也需要调用_CrtDumpMemoryLeaks打印报告(可通过程序入口出调用_CrtSetDbgFlag来避免对_CrtDumpMemoryLeaks的直接调用)。

二、对于MFC程序
MFC工程,最终也是按Win32工程方式的内存泄漏执行,不过MFC工程创建向导生成的MFC工程,自动支持C++分配的内存泄漏检测,我们不需要任何处理。

2.1 对于C++分配的内存
MFC是通过下面的语句支持的:
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

我们简单分析一下:
在MFC中,DEBUG_NEW也是个宏,定义为:
#define DEBUG_NEW new(THIS_FILE, __LINE__)
所以,在MFC debug下,调用new操作符,会使用
void* __cdecl operator new(size_t nSize, LPCSTR lpszFileName, int nLine)
来分配内存,而operator new最终会调用到_heap_alloc_dbg_impl,并把分配内存的文件名、行号传递给_heap_alloc_dbg_impl。
当我们调用delete删除内存时,operator delete函数会最终执行_free_dbg_nolock,而_free_dbg_nolock内部,会把待删除的指针的记录,从_pFirstBlock链表中删除。
当程序结束时,_pFirstBlock会检测链表中未删除的内存,给出内存泄漏报告。

需要注意的是,并不是每个cpp文件中,都定义了DEBUG_NEW,尤其后添加的文件。

2.2 对于C分配的内存。
默认不显示文件名和行号。我们最终的目的,是让对malloc等函数的调用,调用到_malloc_dbg等函数上(因为只有_malloc_dbg才接收带文件名和行号的参数)。而<crtdbg.h>中,已把malloc映射到_malloc_dbg上了。但MFC程序,也做了对malloc的映射。所以,我们只要在MFC程序的stdafx.h文件中、在
#include <afxwin.h>         // MFC core and standard components
之前,包含下面的代码即可:
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

注意,这些映射有_CRTDBG_MAP_ALLOC这个条件宏,所以要先定义它。

三、总结(这部分要全部看完再编写代码):
我们验证VC是否启动用了详细内存泄漏信息检测的方法,一个是运行一次程序,看报告是否包含文件名和行号;另一种方法是单步调试new或malloc调用,如果能进入带文件名和行号参数的函数,就表示打印报告时,可以输出文件名和等号。

Win32程序,检测c和C++内存泄漏的通知做法是:

a. 为了检测C语言内存泄漏,按顺序包含头文件(可放到stdafx.h文件中):
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

#define IUI_DEBUG_NEW    new(_NORMAL_BLOCK, __FILE__, __LINE__)

b. 为了检测C++内存,在每一个需要检测内存的cpp文件中,定义宏(位置在所有#include 之后):

#ifdef _DEBUG
#define new    IUI_DEBUG_NEW
#endif // _DEBUG

c. 为了在程序结束时可以打印泄漏报告,在程序入口处调用:
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

MFC程序检测c和C++内存泄漏的通知做法是:
a. 在stdafx.h文件中、在
#include <afxwin.h>         // MFC core and standard components
之前,包含下面的代码:
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

#define IUI_DEBUG_NEW    new(__FILE__, __LINE__)

b. 在每一个需要检测内存的cpp文件中,定义宏(位置在所有#include 之后):
#ifdef _DEBUG
#define new IUI_DEBUG_NEW // 其实,只执行b步骤,并且,把IUI_DEBUG_NEW换成DEBUG_NEW也行。
#endif

如果放到#include <afxwin.h> 之前,会有编译错误。

之所以#define new DEBUG_NEW不放到stdafx.h文件中,是因为:定义的new,可能和gdiplus不兼容,因为gdiplus里,也重载了operator new,如果放到stdafx.h文件中,会比gdiplus的头文件先包含。导致编译gdiplus时错误。
另外,由于我们在cpp中,通常是第一个包含stdafx.h,之后还会包含其它头文件,而这些头文件,可能又重新定义了new,导致我们的new定义被覆盖。所以,安全的做法有两种:
一种是,专门做一个头文件,如DumpMemoryLeaks.h,内容如下:
#pragma once

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

然后在每个cpp中,包含了所有头文件之后,包含
#include "DumpMemoryLeaks.h"

另一种是直接在每个cpp中包含了所有头文件之后,直接宏定义:
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

我们推荐在cpp中直接使用宏定义,因为如果包含DumpMemoryLeaks.h,用户往往会在包含了DumpMemoryLeaks.h之后,再包含其它头文件。而我们希望的是,DumpMemoryLeaks.h在所有其它头文件包含之后再包含。但写成宏定义的话,很少人会在宏定义后再包含其它头文件。

四、思考:
1. 如何做到在程序结束时,如果有内存泄漏,就弹出断言。
2. 如何让检测到的泄漏报告中,包含分配时的调用栈

五,具体实施。
1. 先用windows的搜索功能,搜索DEBUG_NEW,把所有包含DEBUG_NEW的cpp文件,都删除到回收站(一定要进回收站,方便改造完恢复。结合版本控件软件,做到可恢复双保险)
2. 用RemoveFile.exe把漏网之鱼再删除到回收站。
3. 用VS的查找功能,看是否仍然有漏网之鱼。如果有,手工删除。
4. 用EMEditor的DumpMemoryLeaks.jsee宏,为剩余的CPP文件,插入#define new宏。

附:
DEBUG_NEW或自己定义的new宏,有可能与gdiplus的Gdiplus::GdiplusBase::operator new的冲突,在编译时,会收到编译错误:
error C2660: 'Gdiplus::GdiplusBase::operator new' : function does not take 3 arguments
解决方案有两种:
1. 注释掉自己写的new宏或DEBUG_NEW,但这样,将失去内存泄漏输出详细信息的功能。
2. 为gdi+提供重载的new和delete函数接受附加参数。下面是已写好的代码,把它放到一个新的头文件NewGdiplus.h中,然后包含NewGdiplus.h代替包含Gdiplus.h.

 Ensure that GdiPlus header files work properly with MFC DEBUG_NEW and STL header files.#define iterator _iterator#ifdef _DEBUGnamespace Gdiplus
{namespace DllExports{#include <GdiplusMem.h>};#ifndef _GDIPLUSBASE_H#define _GDIPLUSBASE_Hclass GdiplusBase{public:void (operator delete)(void* in_pVoid){DllExports::GdipFree(in_pVoid);}void* (operator new)(size_t in_size){return DllExports::GdipAlloc(in_size);}void (operator delete[])(void* in_pVoid){DllExports::GdipFree(in_pVoid);}void* (operator new[])(size_t in_size){return DllExports::GdipAlloc(in_size);}void * (operator new)(size_t nSize, LPCSTR lpszFileName, int nLine){return DllExports::GdipAlloc(nSize);}void operator delete(void* p, LPCSTR lpszFileName, int nLine){DllExports::GdipFree(p);}};#endif // #ifndef _GDIPLUSBASE_H
}
#endif // #ifdef _DEBUG#include <gdiplus.h>
#undef iteratorEnsure that Gdiplus.lib is linked.
#pragma comment(lib, "gdiplus.lib")

参考:https://support.microsoft.com/en-us/kb/317799

其它方法:

使用gflags.exe和windbg.exe提供了另一种检测内存泄漏的方法。

--------------------- 
版权声明:本文为CSDN博主「psbeond」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/psbeond/article/details/99546363

(转载)VC的内存泄漏检查相关推荐

  1. Linux 下几款程序内存泄漏检查工具

    Linux 下几款程序内存泄漏检查工具 chenyoubing | 发布于 2016-07-23 10:08:09 | 阅读量 93 | 无 写这篇博客的原因呢是因为自己在编写基于Nginx磁盘缓存管 ...

  2. linux c 代码分析工具,编程达人 分享几款Linux 下C/C++程序内存泄漏检查工具

    1.内存管理是否正确(因为这个程序本身开辟很多内存空间进行缓存管理,同时这个程序程序本身就是基于C/C++开发的,内存管理机制一直是程序员头痛的东西) 2.程序的健硕性如何(服务器任何程序的基本要求就 ...

  3. 常用的内存泄漏检查工具

    以下是一些常用的内存泄漏检查工具: 1. Valgrind:Valgrind是一种开源的内存调试和性能分析工具,可以检测内存泄漏.使用未初始化的内存.使用已释放的内存等问题. 2. AddressSa ...

  4. (Android Studio 3.0)Android Profiler内存泄漏检查

    前提概要 内存泄漏是常见又重要的问题,针对这个问题谷歌在Android Studio 3.0中推出了Android Profiler.笔者此篇文章主要记录一下Android Profiler在内存泄漏 ...

  5. 【内存检测系列】window下windbg内存泄漏检查

    文章目录 1.使用windbg中的umdh检查 1.1. 工具下载 1.2.环境配置 1.3.利用工具umdh(user-mode dump heap)分析 1.4.利用umdh创建heap快照 2. ...

  6. 【转载】gdi+ 内存泄漏

    [转载]http://issf.blog.163.com/blog/static/1941290822009111894413472/ 最近用GDI+实现了几个自定义控件,但是发现存在内存泄露问题 B ...

  7. 内存泄漏检测工具(转载)

    内存泄漏检测工具2007年08月08日 1.     ccmalloc-Linux和Solaris下对C和C++程序的简单的使用内存泄漏和malloc调试库. 2.     Dmalloc-Debug ...

  8. Android内存泄漏

    Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的 ...

  9. 电脑是否存在内存泄漏_如何避免内存泄露

    如何避免内存泄露 关于内存泄露的原因有很多,最常见的有如下几种: 1 . 对于通过 new 等运算符申请到的内存空间在使用之后没有释放掉. 关于这个问题, 如果 是在过程程序中开辟的空间, 我们可以在 ...

最新文章

  1. Android替换view父节点,android – issue:指定的子节点已经有父节点.您必须首先在孩子的父母上调用removeView()...
  2. RESTful之认证Authentication
  3. Ext.data-Store
  4. 机器学习知识点(三十七)特征选择方法总结
  5. 使用Maven archetype 自定义项目脚手架
  6. [js] js怎样避免原型链上的对象共享?
  7. 什么样的外链才是高质量的外链|网站优化
  8. python vue token_Haytham个人博客开发日志 -- Flask+Vue基于token的登录状态与路由管理...
  9. 2015-2016书籍计划
  10. ansys命令流——点线面体基础操作(02)
  11. 制作PPT的常用网站及一些基本原则
  12. 吉林大学软件学院软件构件与中间件技术
  13. Matlab图像的二维傅里叶变换频谱图特点研究
  14. Linux下内存情况分析
  15. 【总结】计算机网络原理
  16. 小工具:输出Houdini里节点间参数的差别
  17. Git 工具 - 储藏(Stashing)
  18. 关于在控制面板上软件卸载失败的问题
  19. IOS 多线程之信号量
  20. 原生JavaScript实现团购——限时抢

热门文章

  1. 中国拟薄水铝石市场产销情况及投资前景预测报告2022-2027年
  2. 解决Docker Swarm重装.yml文件后无法创建新Service
  3. 深拷贝的五种实现方式
  4. python画卡通_Python动画底纹matplotlib
  5. 基于博弈论的出租车系统效率优化
  6. すぬけ君の塗り絵 2 イージー / Snuke's Coloring 2 (ABC Edit) AtCoder - 2145
  7. 这几个程序员必备画图工具,很实用!
  8. java host头攻击漏洞_Java Web项目漏洞:检测到目标URL存在http host头攻击漏洞解决办法...
  9. Java 从键盘输入一个字符串,统计某个字符的个数
  10. 无法将值vmware-tray.exe写入注册表