学习范例http://www.cppblog.com/Robertxiao/archive/2012/11/05/194547.html

在使用MFC库开发程序时,我非常喜欢MFC框架中的内存泄漏诊断机制,它的确能很好地帮助我们查找出内存泄漏。可是链接了MFC库也使得生成的可执行文件大了许多,这个没什么负面影响。

最可怕的是如果仅为了使用内存诊断机制,而带来了链接库冲突的麻烦。我也是在遇到这个问题时,才写出了一个简易的内存诊断机制。

一般大家都误以为这些内存泄漏的检测功能是由MFC提供的,其实不然。MFC只是封装和利用了MS C-Runtime Library的Debug Function。

非MFC程序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检测功能。MS C-Runtime Library在实现malloc/free,strdup等函数时已经内建了内存泄漏的检测功能。

注意观察一下由MFC Application Wizard生成的项目,在每一个cpp文件的头部都有这样一段宏定义:

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

有了这样的定义,在编译DEBUG版时,出现在这个cpp文件中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢?DEBUG_NEW也是一个宏,以下摘自afx.h,1632行

#define DEBUG_NEW new(THIS_FILE, __LINE__)

所以如果有这样一行代码:

char* p = new char[200];

经过宏替换就变成了:

char* p = new( THIS_FILE, __LINE__)char[200];

根据C++的标准,对于以上的new的使用方法,编译器会去找这样定义的operator new:

void* operator new(size_t, LPCSTR, int)

我们在afxmem.cpp 63行找到了一个这样的operator new 的实现

void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine)

{

 return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);

}

void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine)

{

 …

 pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine);

 if (pResult != NULL)

  return pResult;

 …

}

  第二个operator new函数比较长,为了简单期间,我只摘录了部分。很显然最后的内存分配还是通过_malloc_dbg函数实现的,这个函数属于MS C-Runtime Library 的Debug Function。这个函数不但要求传入内存的大小,另外还有文件名和行号两个参数。文件名和行号就是用来记录此次分配是由哪一段代码造成的。如果这块内存在程序结束之前没有被释放,那么这些信息就会输出到Debug窗口里。

  这里顺便提一下THIS_FILE,__FILE和__LINE__。__FILE__和__LINE__都是编译器定义的宏。当碰到__FILE__时,编译器会把__FILE__替换成一个字符串,这个字符串就是当前在编译的文件的路径名。当碰到__LINE__时,编译器会把__LINE__替换成一个数字,这个数字就是当前这行代码的行号。在DEBUG_NEW的定义中没有直接使用__FILE__,而是用了THIS_FILE,其目的是为了减小目标文件的大小。假设在某个cpp文件中有100处使用了new,如果直接使用__FILE__,那编译器会产生100个常量字符串,这100个字符串都是飧?/SPAN>cpp文件的路径名,显然十分冗余。如果使用THIS_FILE,编译器只会产生一个常量字符串,那100处new的调用使用的都是指向常量字符串的指针。

  再次观察一下由MFC Application Wizard生成的项目,我们会发现在cpp文件中只对new做了映射,如果你在程序中直接使用malloc函数分配内存,调用malloc的文件名和行号是不会被记录下来的。如果这块内存发生了泄漏,MS C-Runtime Library仍然能检测到,但是当输出这块内存块的信息,不会包含分配它的的文件名和行号。

要在非MFC程序中打开内存泄漏的检测功能非常容易,你只要在程序的入口处加入以下几行代码:

int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

tmpFlag |= _CRTDBG_LEAK_CHECK_DF;

_CrtSetDbgFlag( tmpFlag );

  这样,在程序结束的时候,也就是winmain,main或dllmain函数返回之后,如果还有内存块没有释放,它们的信息会被打印到Debug窗口里。

如果你试着创建了一个非MFC应用程序,而且在程序的入口处加入了以上代码,并且故意在程序中不释放某些内存块,你会在Debug窗口里看到以下的信息:

{47} normal block at 0x00C91C90, 200 bytes long.

Data: < > 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

  内存泄漏的确检测到了,但是和上面MFC程序的例子相比,缺少了文件名和行号。对于一个比较大的程序,没有这些信息,解决问题将变得十分困难。

  为了能够知道泄漏的内存块是在哪里分配的,你需要实现类似MFC的映射功能,把new,maolloc等函数映射到_malloc_dbg函数上。这里我不再赘述,你可以参考MFC的源代码。

  由于Debug Function实现在MS C-RuntimeLibrary中,所以它只能检测到堆内存的泄漏,而且只限于malloc,realloc或strdup等分配的内存,而那些系统资源,比如HANDLE,GDI Object,或是不通过C-Runtime Library分配的内存,比如VARIANT,BSTR的泄漏,它是无法检测到的,这是这种检测法的一个重大的局限性。另外,为了能记录内存块是在哪里分配的,源代码必须相应的配合,这在调试一些老的程序非常麻烦,毕竟修改源代码不是一件省心的事,这是这种检测法的另一个局限性。

//头文件
#pragma once
#include <crtdbg.h>
#include <malloc.h> #ifdef _DEBUG
#define THIS_FILE __FILE__
//添加宏定义,使用malloc分配内存也能检测到内存泄漏
#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) //设置使用new分配内存能检测到内存泄漏
void* _cdecl operator new(size_t nSize, int nType, const char* lpszFileName, int nLine); //覆盖operator new 和 delete 运算符,
void* _cdecl operator new(size_t nSize, const char* lpszFileName, int nLine);
void* __cdecl operator new[](size_t nSize, const char* lpszFileName, int nLine);
void __cdecl operator delete(void* p);
void __cdecl operator delete[](void* p); #define DEBUG_NEW new(THIS_FILE, __LINE__)
#else
#define DEBUG_NEW new
#endif //_DEBUG //mfc的afx.h头文件中关于new覆盖的定义
// Memory tracking allocation //void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
//#define DEBUG_NEW new(THIS_FILE, __LINE__)
//void AFX_CDECL operator delete(void* p, LPCSTR lpszFileName, int nLine);
//
//void * __cdecl operator new[](size_t);
//void* __cdecl operator new[](size_t nSize, LPCSTR lpszFileName, int nLine);
//void __cdecl operator delete[](void* p, LPCSTR lpszFileName, int nLine);
//void __cdecl operator delete[](void *);
//实现文件
//#include "StdAfx.h"
#include <malloc.h>
#include "DebugNew.h" namespace
{
class AutoDetectMemory
{
public:
AutoDetectMemory()
{
#ifdef _DEBUG
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
}
}; static AutoDetectMemory gs_am; } void* _cdecl operator new(size_t nSize, const char* lpszFileName, int nLine)
{
return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);
} void* __cdecl operator new[](size_t nSize, const char* lpszFileName, int nLine)
{
return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);
} void __cdecl operator delete(void* p)
{
#ifdef _DEBUG
_free_dbg(p, _NORMAL_BLOCK);
#else
free(p);
#endif
} void __cdecl operator delete[](void* p)
{
::operator delete(p);
} void* __cdecl operator new(size_t nSize, int nType, const char* lpszFileName, int nLine)
{
#ifdef _DEBUG
return _malloc_dbg(nSize, nType, lpszFileName, nLine);
#else
//UNREFERENCED_PARAMETER(nType);
//UNREFERENCED_PARAMETER(lpszFileName);
//UNREFERENCED_PARAMETER(nLine);
return ::operator new(nSize);
#endif
}

测试结果:

使用方式:

在stdafx.h中包含头文件

在要用诊断机制的文件中加入如下的代码。
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

这样不管是new申请的内存泄漏,还是malloc申请的内存泄漏在程序正常退出后都会输出在Debug窗口里。而且都会显示完整文件路径、行数、泄漏字节数。

学习打造自己的DEBUG_NEW的更多相关文章

  1. C语言编程学习打造——做题游戏

    C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现 ...

  2. 移动应用开发者最应该知道的8款SDK

    2017年双11全球狂欢节结束后,据大数据公司统计显示,2017年双11全网销售额达2539.7亿,移动端销售占比91.2%.不难看出,智能手机因随身携带.时刻在线等特点,已取代PC,成为网络生活新的 ...

  3. TensorFlow练习13: 制作一个简单的聊天机器人

    现在很多卖货公司都使用聊天机器人充当客服人员,许多科技巨头也纷纷推出各自的聊天助手,如苹果Siri.Google Now.Amazon Alexa.微软小冰等等.前不久有一个视频比较了Google N ...

  4. 入门ROS教程与视频汇总(kinetic)

    参考网址: Richard Wang    3 Shawn Chen 部分视频网址: http://v.youku.com/v_show/id_XMjUxMTc5MzE5Mg http://i.you ...

  5. 爬虫--requests讲解

    什么是requests? Requests是用Python语言编写,基于urllib,采用Apache2 Licensed 开源协议的HTTP库.它比urllib更加方便,可以节约我们大量的工作,完全 ...

  6. 5G新基建到来,图扑推出智慧路灯三维可视化方案

    前言 作为智慧城市的重要组成部分,智慧灯杆管理系统采用信息化.数字化手段,把路灯及城市景观照明等各种不同对象的监控和数据采集及处理融于一体, 为城市管理者进行城市管理.进行科学决策提供了强有力的手段. ...

  7. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

随机推荐

  1. UVA 10173 (几何凸包)

    判断矩形能包围点集的最小面积:凸包 #include <iostream> #include <cmath> #include <cstdio> #include ...

  2. Java-->Json解析网页数据

    --> 官方解析jar包: 链接:http://pan.baidu.com/s/1pKDnXKv 密码:694d --> 离线Json格式检测工具: 链接:http://pan.baidu ...

  3. ES6 - promise对象

    Promise的设计初衷 我们使用ajax请求数据,得到数据后再对数据进行操作,可是有时候,对得到的数据进行操作的过程中,可能又要用到ajax请求,这时,我们的代码就变成了这样: $.ajax({ s ...

  4. (转)深度学习主机环境配置: Ubuntu16.04+Nvidia GTX 1080+CUDA8.0

      深度学习主机环境配置: Ubuntu16.04+Nvidia GTX 1080+CUDA8.0 发表于2016年07月15号由52nlp 接上文<深度学习主机攒机小记>,这台GTX10 ...

  5. 抓包工具PowerSniff-0.1

    做这个程序的意图是wireshark插件编写复杂(虽然也支持lua),而轻量级的工具如smartsniff,minisniff不支持插件化数据分析,各种工具用下来或多或少不顺手.以前写的外挂也都是手工 ...

  6. maven项目修改java编译版本的方式

    背景 使用 maven 3.x 安装到本地后,创建的项目一般都是基于JDK1.5版本.而目前大多数的项目已经升级到1.6或以上,尤其是Servlet3.0 已经要求Java6或以上版本的环境,往往需要 ...

  7. Linux下编译安装PCRE库

    备注:如果没有root权限,使用 --prefix 指定安装路径 ./configure --prefix=/home/work/tools/pcre-8.xx =================== ...

  8. js 实现ActiveXObject("Scripting.Dictionary") 功能

    /* 字典 ActiveXObject("Scripting.Dictionary") 项目中用到的ActiveXObject("Scripting.Dictionary ...

  9. ruby 查询mysql方法

    首先对需要使用的数据库进行封装,便于使用:数据库表封装源码: mysqlapi.rb #业务涉及的数据库的配置ActiveRecord::Base$db1={:adapter => " ...

  10. 【uTenux实验】事件标志

    事件标志是一个用来实现同步的对象,由多个位组成,用作指示对应事件存在的标志.事件标志由用来指示对应事件存在的位模式(bitpattern)和一个等待事件标志的任务队列组成. uTenux提供了一组AP ...