学习打造自己的DEBUG_NEW

学习范例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窗口里。而且都会显示完整文件路径、行数、泄漏字节数。

时间: 2024-10-06 15:10:39

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

【深度学习系列4】深度学习及并行化实现概述

[深度学习系列4]深度学习及并行化实现概述 摘要: 深度学习可以完成需要高度抽象特征的人工智能任务,如语音识别.图像识别和检索.自然语言理解等.深层模型是包含多个隐藏层的人工神经网络,多层非线性结构使其具备强大的特征表达能力和对复杂任务建模能力.训练深层模型是长期以来的难题,近年来以层次化.逐层初始化为代表的一系列方法的提出给训练深层模型带来了希望,并在多个应用领域获得了成功.深层模型的并行化框架和训练加速方法是深度学习走向实用的重要基石,已有多个针对不同深度模型的开源实现,Google.Fac

第三百七十六节,Django+Xadmin打造上线标准的在线教育平台—创建用户操作app,在models.py文件生成5张表,用户咨询表、课程评论表、用户收藏表、用户消息表、用户学习表

第三百七十六节,Django+Xadmin打造上线标准的在线教育平台-创建用户操作app,在models.py文件生成5张表,用户咨询表.课程评论表.用户收藏表.用户消息表.用户学习表 创建名称为app_operation的用户操作APP,写数据库操作文件models.py models.py文件 #!/usr/bin/env python # -*- coding:utf-8 -*- from __future__ import unicode_literals from datetime i

学习NodeJS第三天:打造Nodejs的调试环境(中)

2012-12-07 因追加<学习 NodeJS 第三天:打造 Nodejs 的调试环境(下)>的缘故,特此将原来的<下>篇改为<中>篇,如标题所示. 上一期我们为大家介绍了安装 Eclipse 调试插件的情况,这对于还不熟悉 Eclipse 开发平台的用户是至关重要的,希望可以通过一步步的图片加文字说明,把 Nodejs 困难的地方变简单和清晰.友好和轻松. 现在正式进入要调试程序肯定要有调试代码.下面就是我们第一个测试的代码,很小的行数: var sys = req

MVC系列——MVC源码学习:打造自己的MVC框架(四:自定义视图)

前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾.对于这个系列,通过学习源码,博主也学到了很多东西,在此还是把博主知道的先发出来,供大家参考. 本文原创地址:http://www.cnblogs.com/landeanfen/p/6019719.html MVC源码学习系列文章目录: MVC系列——MVC源码学习:打造自己的MVC框架(一) MVC

打造持续学习型组织

本文来源于我在InfoQ中文站翻译的文章,原文地址是:http://www.infoq.com/cn/news/2015/06/continuous-learning-organization 软件开发通常被认为是知识密集型活动,因此各个组织都会寻求能够促进持续学习的手段与方式.Marcin Floryan认为"我们需要学习型组织,并且从个体学习开始".不过,个体学习有时会很困难,大规模的个体学习变得更加富有挑战.那么,如何才能打造持续学习型组织呢? 在Lean Kanban Cent

张玮桐_打造open、快乐的学习环境

张玮桐一直认为学习环境对于孩子的教育有着至关重要的作用,俗话说人事环境的产物,这句话也不是一点道理都没有,所以我们更应该把注意力放在为孩子打造一些轻松,快乐的学习环境,这样对孩子的学习也会有很大的帮助. 80后团队打造open.快乐的学习环境 今年5月,从美国引进,隶属于睿稚集团的"小小地球少儿英语"在上海宣布,公司将发力全国少儿英语培训市场,预计3年内在全国新扩50个点,总规模将达到150家教学中心. 记者了解到,和培育少儿自信.团队协作精神的教育模式大相径庭的是,支撑起"

【Linux学习之旅】之Ubuntu/Linux打造中文版man帮助手册

一.如何安装中文的man包 本项目的主页为: http://code.google.com/p/manpages-zh/ 本项目(manpages-zh)为 i18n-zh 项目[1]的子项,从 CMPP (中文 Man Pages 计 划) 分支而来. [1] http://code.google.com/p/i18n-zh CMPP 项目现在可能已经死亡,原主页(cmpp.linuxforum.net)已不能访问. 本项目的目的是维护 CMPP 遗留下的成果,并对其错误/漏洞进行修改. 本项

打造我们的学习

打造我们俱乐部的学习,如打造 CSDN 这个品牌一样认真. 年长一届,长的,可能更多的是经历.知识上面肯定是有进步的,但就我和不少小伙伴而言肯定是切身的感受更多一些,成不成熟,哈哈,这还真是很待商榷. 我们在进行和小伙伴们一同的学习的时候要注意的方面还是很多的.要不然,稍有不慎,你所给予的只是经历了,而且还会白白耗费双方的精力.我今天又有些无聊的,嘿嘿,说说这一段时间我在这些事儿上的思考. "目标上大展宏图,发展上脚踏实地."我们是一个技术型社团,追求的固然是一群极客天天"发

英语学习自测神器——用python爬虫打造专属英文词汇量测试脚本!

英语是现在传播很广的一门语言,它是由26个字母组成,像小编这种小时候不喜欢学英语的人,一直都是看见英语就头大.后来发现这东西不学不行,还是咬着牙学了.大家应该都知道学习英语,最重要的是词汇量,所以今天我们就用python做一个小的爬虫,然后自己写一个脚本来实现这个功能吧! 目标:打造一个英文词汇量测试脚本 url: 扇贝网 工具: python3.6.pycharm.requests库 思路: 和网页一样,总共分三步: 先来看看网页中数据,用F12开发者工具抓包就可以 为了更好的演示,这里网页缩