1、概述

在Windows下微软给我们提供了一个十分强大的C/C++运行时库,这个运行时库中包含了很多有用的功能。而众多强大功能之一就是内存泄露的检测。

C/C++提供了强大的内存管理功能,不过随之而来的却是内存管理的复杂问题。内存泄露、踩内存等问题随之大量产生。要完全杜绝这些问题是比较困难,不过一个高效有用的工具却可以将内存泄露的问题第一时间发现并处理掉。

VS的C/C++运行时库中内存管理系统的基础就是调试堆,调试堆和普通的堆不同之处就在于每一块分配的内存前后都有一些额外的信息。下面就是每块分配内存的额外信息:

  1. typedef struct _CrtMemBlockHeader
  2. {
  3. struct _CrtMemBlockHeader * pBlockHeaderNext;
  4. struct _CrtMemBlockHeader * pBlockHeaderPrev;
  5. char * szFileName;
  6. int nLine;
  7. #ifdef _WIN64
  8. /* These items are reversed on Win64 to eliminate gaps in the struct
  9. * and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is
  10. * maintained in the debug heap.
  11. */
  12. int nBlockUse;
  13. size_t nDataSize;
  14. #else /* _WIN64 */
  15. size_t nDataSize;
  16. int nBlockUse;
  17. #endif /* _WIN64 */
  18. long lRequest;
  19. unsigned char gap[nNoMansLandSize];
  20. /* followed by:
  21. * unsigned char data[nDataSize];
  22. * unsigned char anotherGap[nNoMansLandSize];
  23. */
  24.   } _CrtMemBlockHeader;

从上面的定义我们看到每一块分配的内存(假设的data变量)前后都有一个NoMansLoad的gap。这个gap会填充一些数据,检测是否内存操作超出了合法的范围。szFileName和nLine则表示该内存是从什么文件的哪一行分配的。nDataSize表示该块内存的实际大小,nBlockUse表示当前分配的内存的类型号是什么,类型号稍后会有详细解释。lRequest表示当前分配的序号,这个序号是进程唯一的,第一次分配的为1,第二次为2,以此类推。

C/C++无论使用new、malloc、strdup等最后都会到一个CRT的内部函数:_heap_alloc_dbg_impl。这个函数的作用就是填充上面看到的内存块的头和尾,然后做一些HOOK函数以及检查调试堆的工作。

有了上面关于调试堆管理内存的基本了解以后,我们通过MSDN(ms-help://MS.MSDNQTR.v90.chs/dv_vccrt/html/cb4d2664-10f3-42f7-a516-595558075471.htm)可以看到一些CRT内存状态查看、管理的函数。

其中几个对内存泄露检测有用的函数是:

_CrtDumpMemoryLeaks:将目前尚未释放的内存信息打印出来;

_CrtMemCheckpoint:建立一个当前堆上内存使用的快照;

_CrtMemDifference:比较两个快照之间的内存块变化;

_CrtSetBreakAlloc:指定在第几次内存分配的时候中断;

_CrtSetDbgFlag:设置一些调试的标志。

2、 基本使用方法

下面我们通过几个实例来简单介绍内存泄露的使用方法:

1. 简单使用_CrtDumpMemoryLeaks:

  1. #include "crtdbg.h"
  2. int main()
  3. {
  4. int *leakptr = (int *)malloc( * sizeof(int));
  5. memset(leakptr, 0x5f, * sizeof(int));
  6. _CrtDumpMemoryLeaks();
  7. return ;
  8. }

输出结果:

Detected memory leaks!

Dumping objects ->

{54} normal block at 0x00393190,  bytes long.

Data: <________________> 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F

Object dump complete.

其中,绿色部分表示分配的内存序号,也就是该内存为第几块内存。红色部分为内存块的有效长度,蓝色部分为该内存的前16个字节。橙色部分为该内存的地址。

有了这些我们已经能基本定位非常简单的内存泄露了。不过稍复杂一点的程序都有很多出口,在每个出口都放一个_CrtDumpMemoryLeaks是不合适的。同时很多全局变量也是在main之后析构的。所以实际中我们更加愿意使用下面的方法。

2. 增加_CRTDBG_LEAK_CHECK_DF标志

  1. typedef std::list<int> intlist;
  2. class A
  3. {
  4. intlist* m_var;
  5. public:
  6. A(){ m_var = new intlist; }
  7. ~A(){ delete m_var; }
  8. };
  9. A g_a;
  10. int main()
  11. {
  12. _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
  13. int *leakptr = (int *)malloc( * sizeof(int));
  14. memset(leakptr, 0x5f, * sizeof(int));
  15. //_CrtDumpMemoryLeaks();
  16. return ;
  17. }

输出结果为:

Detected memory leaks!

Dumping objects ->

{54} normal block at 0x00393190, 400 bytes long.

Data: <________________> 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F

Object dump complete.

如果不使用_CrtSetDbgFlag而使用_CrtDumpMemoryLeaks输出结果为:

Detected memory leaks!

Dumping objects ->

{122} normal block at 0x00396248, 400 bytes long.

Data: <________________> 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F

{121} normal block at 0x00396200, 12 bytes long.

Data: < b9  b9     > 00 62 39 00 00 62 39 00 CD CD CD CD

{120} normal block at 0x003961A8, 24 bytes long.

Data: <                > 00 00 00 00 CD CD CD CD CD CD CD CD CD CD CD CD

Object dump complete.

也就是说_CrtDumpMemoryLeaks仅能保证获取当前尚未释放的内存。而通过设置_CRTDBG_LEAK_CHECK_DF标志对于内存泄露检测更加有意义!

遗憾的是MFC使用的前一种,所以MFC默认检测的内存泄露意义不大。

3. 获取内存泄露的位置

  1. #define _CRTDBG_MAP_ALLOC
  2. #include "crtdbg.h"
  3. int main()
  4. {
  5. _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
  6. int *leakptr = (int *)malloc( * sizeof(int) );
  7. memset(leakptr, 0x5f, * sizeof(int));
  8. return ;
  9. }

输出:

Detected memory leaks!

Dumping objects ->

e:\vsprj\win32_test\mfc_console\mfc_console.cpp() : {122} normal block at 0x00396248, 400 bytes long.

Data: <________________> 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F

Object dump complete.

我们看到红色的文件名和绿色的行号都出现了。对于这个我们只需要非常简单地增加两行语句:

#define  _CRTDBG_MAP_ALLOC

#include "crtdbg.h"

4. C++中new如何享受到这种便利

C++中的new可以支持替换,这给我们提供了一个非常方便的解决方案。而CRT也提供了Debug版本的new,原型如下:

void *__CRTDECL operator new[](

size_t cb,

int nBlockUse,

const char * szFileName,

int nLine

)

void *__CRTDECL operator new(

size_t cb,

int nBlockUse,

const char * szFileName,

int nLine

)

所以我们的思路就是将原来的new替换为debug版本的new。

  1. #define _CRTDBG_MAP_ALLOC
  2. #include "crtdbg.h"
  3. #include <string.h>
  4. #ifdef _DEBUG
  5. #define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__)
  6. #define new DEBUG_CLIENTBLOCK
  7. #else
  8. #define DEBUG_CLIENTBLOCK
  9. #endif
  10. int main()
  11. {
  12. _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
  13. int *leakptr = new int[];
  14. memset(leakptr, 0x5f, * sizeof(int));
  15. return ;
  16. }

输出:

Detected memory leaks!

Dumping objects ->

e:\vsprj\win32_test\mfc_console\mfc_console.cpp(21) : {54} client block at 0x00393190, subtype 0, 400 bytes long.

Data: <________________> 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F

Object dump complete.

5. 块类型的使用

块类型是CRT的一个重要功能,通过它我们可以让内存分配检查的粒度更加细化。所谓的快内存就是每次分配的时候指定的块号。块类型目前总共五种:

_NORMAL_BLOCK:就是我们日常分配的默认类型;

_CRT_BLOCK:CRT自己使用的内存;

_CLIENT_BLOCK:就是给我们自己用的客户类型,可以指定子类型号,也是我们需要重点关注的类型;

_FREE_BLOCK:已经释放的块,因为CRT可以让链表上保留已经释放的块来模拟内存不足的情况,所以这类内存一般被填充0xDD,块类型标为_FREE_BLOCK。

_IGNORE_BLOCK:有可能在一段时间内关闭调试堆操作。在该时间段内,内存块保留在列表上,但被标记为“忽略”块。

那么_CLIENT_BLOCK是如何使用的呢?还记得调试堆中的nBlockUse吧,这是一个32位的数据,其中低16位表示块的大类型,而高16位则表示子类型。所以每次我们申请内存的时候给出的块类型都包含了大类型和子类型,分别设置他们的高16位和低16位即可。

例如:

#define IGS_SUBTYPE 0x50

#define IGS_BLOCK _CLIENT_BLOCK | ( IGS_SUBTYPE << 16 )

从定义看出IGS_BLOCK表示一个subtype为50的客户端数据块。

下面我们用代码简单说明如何使用客户端数据块。

  1. #define _CRTDBG_MAP_ALLOC
  2. #include "crtdbg.h"
  3. #include <string.h>
  4. #ifdef _DEBUG
  5. #define IGS_SUBTYPE 0x50
  6. #define IGS_BLOCK _CLIENT_BLOCK | ( IGS_SUBTYPE << 16 )
  7. #define IGS_DBGNEW new( IGS_BLOCK, __FILE__, __LINE__)
  8. #define new IGS_DBGNEW
  9. #else
  10. #define DEBUG_CLIENTBLOCK
  11. #endif
  12. int main()
  13. {
  14. _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
  15. int *leakptr = new int[];
  16. memset(leakptr, 0x5f, * sizeof(int));
  17. return ;
  18. }

输出:

Detected memory leaks!

Dumping objects ->

e:\vsprj\win32_test\mfc_console\mfc_console.cpp(23) : {54} client block at 0x00393190, subtype 50, 400 bytes long.

Data: <________________> 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F 5F

Object dump complete.

Windows下C/C++内存泄露检测机制的更多相关文章

  1. Unix下C程序内存泄露检测工具:valgrind的安装使用

    Valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. Valgrind的最初作者是Julian Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Goo ...

  2. Linux下C程序内存泄露检测

    在linux下些C语言程序,最大的问题就是没有一个好的编程IDE,当然想kdevelop等工具都相当的强大,但我还是习惯使用kdevelop工具,由于没有一个习惯的编程IDE,内存检测也就成了在lin ...

  3. vld(Visual Leak Detector) 内存泄露检测工具

    初识Visual Leak Detector 灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题.当程序越来越复 杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题.内存 ...

  4. 【YFMemoryLeakDetector】人人都能理解的 iOS 内存泄露检测工具类

    背景 即使到今天,iOS 应用的内存泄露检测,仍然是一个很重要的主题.我在一年前,项目中随手写过一个简单的工具类,当时的确解决了大问题.视图和控制器相关的内存泄露,几乎都不存在了.后来想着一直就那个工 ...

  5. vld,Bounds Checker,memwatch,mtrace,valgrind,debug_new几种内存泄露检测工具的比较,Valgrind Cheatsheet

    概述 内存泄漏(memory leak)指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况,在大型的.复杂的应用程序中,内存泄漏是常见的问题.当以前分配的一片内存不再需要使用或无法访问时,但是却 ...

  6. 精准 iOS 内存泄露检测工具

    MLeaksFinder:精准 iOS 内存泄露检测工具 发表于 2016-02-22   |   zepo   |   23 Comments 背景 平常我们都会用 Instrument 的 Lea ...

  7. memwatch内存泄露检测工具

    工具介绍 官网 http://www.linkdata.se/sourcecode/memwatch/ 其功能如下官网介绍,挑选重点整理: 1. 号称功能: 内存泄露检测 (检测未释放内存, 即 动态 ...

  8. Visual C++内存泄露检测—VLD工具使用说明[转]

    Visual C++内存泄露检测—VLD工具使用说明 一.        VLD工具概述 Visual Leak Detector(VLD)是一款用于Visual C++的免费的内存泄露检测工具.他的 ...

  9. Visual C++内存泄露检测—VLD工具使用说明

    一.        VLD工具概述 Visual Leak Detector(VLD)是一款用于Visual C++的免费的内存泄露检测工具.他的特点有:可以得到内存泄漏点的调用堆栈,如果可以的话,还 ...

随机推荐

  1. 第十篇.2、python并发编程之多进程

    一 multiprocessing模块介绍 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程.P ...

  2. kvm虚拟机热迁移

    一.热迁移描述: 相比KVM虚拟机冷迁移中需要拷贝虚拟机虚拟磁盘文件,kvm虚拟机热迁移无需拷贝虚拟磁盘文件,但是需要迁移到的宿主机之间需要有相同的目录结构虚拟机磁盘文件,也就是共享存储,本文这部分内 ...

  3. jQuery获取兄弟标签的文本

    // 一个div里面有一个span标签和多个button标签,每个button标签都有id,span标签没有id,通过点击其中一个button标签,来获取到span标签的text function ( ...

  4. Linux三剑客:grep、awk、sed

    ---------------------------------------------------------------------------------------------------- ...

  5. cmd拷贝文件夹时,处理提示

    xcopy 若目标盘上不存在此子目录,而在目标盘的结束符又不以"\"为结束,则将提示: does destination specify a file name or direct ...

  6. 多态(Polymorphism)的实现机制

    1. 我理解的广义的 override 是指抛开各种访问权限,子类重定义(redefine)父类的函数(即函数签名相同). 2. C++中的三个所谓的原则:never redefine base cl ...

  7. Java常用类库——观察者设计模式

    观察者设计模式 现在很多的购房者都在关注着房子的价格变化,每当房子价格变化的时候,所有的购房者都可以观察得到.实际上以上的购房者都属于观察者,他们都关注着房子的价格. 如果要想实现观察者模式,则必须依 ...

  8. 从rc文件访问字符串

    有.rc文件,其中包含用于exe文件详细信息的版本,说明等. 如何获得在代码内使用的值?例如,要获取ProductName. IDI_ICON1 ICON DISCARDABLE "abc- ...

  9. h5手机页面注册处理(短信验证)

    //获取验证码 var wait = 60; function time(o) { if(wait == 0) { o.removeAttribute("disabled"); o ...

  10. [深度学习] centos7上搭建基于Anaconda3的caffe+pycaffe环境(python3.6)

    本文记录从零开始在CentOS7.x系统上搭建Caffe深度学习平台,并配置pycaffe环境.(由于在虚拟机上搭建,所以为CPU_ONLY模式) 1.选择CentOS7 mini版镜像安装虚拟机 镜 ...