当前位置: 首页 > 编程日记 > 正文

C++ 检测内存泄露

本文描述了如何检测内存泄露。最主要的是纯C,C++的程序如何检测内存泄露。

现在有很多专业的检测工具,比如比较有名的BoundsCheck, 但是这类工具也有他的缺点,我认为首先BoundsCheck是商业软件,呵呵。然后呢需要安装,使用起来不太方便。因为我们检测的时候不一定经常会启动他来检测。这样经常会积累很多问题,那时要解决就麻烦了。最好就是从开始编码,一步一步的都能随时提醒我们内存泄露。我们编程序会经常调试,假如能在每次调试程序的时候都能自动检测内存泄露就好了。

 一. 在 MFC 中检测内存泄漏

假如是用MFC的程序的话,很简单。默认的就有内存泄露检测的功能。
我们用VS2005生成了一个MFC的对话框的程序,发现他可以自动的检测内存泄露.不用我们做任何特殊的操作. 仔细观察,发现在每个CPP文件中,都有下面的代码:


#ifdef _DEBUG
#define new DEBUG_NEW
#endif
DEBUG_NEW 这个宏定义在afx.h文件中,就是它帮助我们定位内存泄漏。

在含有以上代码的cpp文件中分配内存后假如没有删除,那么停止程序的时候,VisualStudio的Output窗口就会显示如下的信息了:

Detected memory leaks!
Dumping objects ->
d:/code/mfctest/mfctest.cpp(80) : {157} normal block at 0x003AF170, 4 bytes long.
 Data: < > 00 00 00 00 
Object dump complete.

在Output窗口双击粗体字那一行,那么IDE就会打开该文件,定位到该行,很容易看出是哪出现了内存泄露。

二.检测纯C++的程序内存泄露

我试了下用VisualStudio建立的Win32 Console Application和Win32 Project项目,结果都不能检测出内存泄露。
下面一步一步来把程序的内存泄露检测的机制建立起来。
  首先,我们需要知道C运行库的Debug版本提供了许多检测功能,使得我们更容易的Debug程序。在MSDN中有专门的章节讲这个,叫做Debug Routines,建议大家先看看里面的内容吧。
  我们会用到里面很重要的几个函数。其中最重要的是 _CrtDumpMemoryLeaks();自己看MSDN里的帮助吧。使用这个函数,需要包含头文件crtdbg.h
该函数只在Debug版本才有用,当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在“Output(输出)”窗口中显示内存泄漏信息.写段代码试验一下吧,如下:

检测内存泄露版本一:

#include "stdafx.h"
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[])
{
    int* p = new int();
    _CrtDumpMemoryLeaks();
    return 0;
}
  运行后,在Output(输出)窗口,显示了如下的信息:


Detected memory leaks!
Dumping objects ->
{112} normal block at 0x003AA770, 4 bytes long.
 Data: <    > 00 00 00 00 
Object dump complete.
 但是这个只是告诉我们程序有内存泄露,到底在哪泄露了一眼看不出来啊。

看我们的检测内存泄露版本二:

#include "stdafx.h"
#ifdef _DEBUG
#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
#define DEBUG_CLIENTBLOCK
#endif
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif
int _tmain(int argc, _TCHAR* argv[])
{
    int* p = new int();
    _CrtDumpMemoryLeaks();
    return 0;
}
该程序定义了几个宏,通过宏将Debug版本下的new给替换了,新的new记录下了调用new时的文件名和代码行.运行后,可以看到如下的结果:

Detected memory leaks!
Dumping objects ->
d:/code/consoletest/consoletest.cpp(21) : {112} client block at 0x003A38B0, subtype 0, 4 bytes long.
 Data: <    > 00 00 00 00 
Object dump complete.
  呵呵,已经和MFC程序的效果一样了,但是等一等。看下如下的代码吧:
int _tmain(int argc, _TCHAR* argv[])
{
    int* p = new int();
    _CrtDumpMemoryLeaks();
    delete p;
    return 0;
}

运行后可以发现我们删除了指针,但是它仍然报内存泄露。所以可以想象,每调用一次new,程序内部都会将该调用记录下来,类似于有个数组记录,假如delete了,那么就将其从数组中删除,而_CrtDumpMemoryLeaks()就是把这个数组当前的状态打印出来。
所以除了在必要的时候Dump出内存信息外,最重要的就是在程序退出的时候需要掉用一次_CrtDumpMemoryLeaks();
 假如程序有不止一个出口,那么我们就需要在多个地方都调用该函数。
 更进一步,假如程序在类的析构函数里删除指针,怎么办?例如:


#include "stdafx.h"
#ifdef _DEBUG
#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
#define DEBUG_CLIENTBLOCK
#endif
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif
class Test
{
public:
    Test()      {   _p = new int();     }
    ~Test()     {   delete _p;          }
    int* _p;
};
int _tmain(int argc, _TCHAR* argv[])
{
    int* p = new int();
    delete p;
    Test t;
    _CrtDumpMemoryLeaks();
    return 0;
}
  可以看到析构函数在程序退出的时候才调用,明明没有内存泄露,但是这样的写法还是报了。

如何改进呢,看检测内存泄露版本三:

#include "stdafx.h"
#ifdef _DEBUG
#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
#define DEBUG_CLIENTBLOCK
#endif
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif
class Test
{
public:
    Test()      {   _p = new int();     }
    ~Test()     {   delete _p;          }
    int* _p;
};
int _tmain(int argc, _TCHAR* argv[])
{
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    int* p = new int();
    delete p;
    Test t;
    return 0;
}

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
该语句在程序退出时自动调用 _CrtDumpMemoryLeaks。必须同时设置 _CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF.
  这样,该版本已经达到了MFC一样的效果了,但是我觉得光这样还不够,因为我们只是在Output窗口中输出信息,对开发人员的提醒还不明显,经常会被遗漏,而且很多人就算发现了内存泄露,但是不好修复,不会严重影响到程序外在表现,都不会修复。怎么样能让开发人员主动的修复内存泄露的问题呢?记得曾经和人配合写程序,我的函数参数有要求,不能为空,但是别人老是传空值,没办法了,只好在函数开始验证函数参数,给他assert住,这样程序运行时老是不停的弹出assert,调试程序那个烦压,最后其他程序员烦了,就把这个问题给改好了,输入参数就正确了。所以我觉得咱要让程序员主动去做一件事,首先要让他觉得做这个事是能减轻自己负担,让自己工作轻松的。呵呵,那咱们也这样,当程序退出时,检测到内存泄露就让程序提示出来。
  看检测内存泄露版本四:

#include "stdafx.h"
#include <assert.h>
#ifdef _DEBUG
#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
#define DEBUG_CLIENTBLOCK
#endif
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif
void Exit()
{
    int i = _CrtDumpMemoryLeaks();
    assert( i == 0);
}
int _tmain(int argc, _TCHAR* argv[])
{
    atexit(Exit);
    int* p = new int();
    return 0;
}
  该版本会在程序退出时检查内存泄露,假如存在就会弹出提示对话框.
 atexit(Exit);设置了在程序退出时执行Exit()函数。
 Exit()函数中,假如存在内存泄露,_CrtDumpMemoryLeaks()会返回非0值,就会被assert住了。

到这个版本已经达到可以使用的程度了。但是我们还可以做些改进,因为真要准确的检测到代码中所有的内存泄露,需要把代码中的#define……拷贝到所有使用new的文件中。不可能每个文件都拷贝这么多代码,所以我们可以将他提取出来,放在一个文件中,比如我是放在KDetectMemoryLeak.h中,该文件内容如下:


#pragma once
#ifdef _DEBUG
#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
#define DEBUG_CLIENTBLOCK
#endif
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif
  然后将KDetectMemoryLeak.h包含在项目的通用文件中,例如用VS建的项目就将其包含在stdafx.h中。或者我自己建的一个Common.h文件中,该文件包含一些通用的,基本所有文件都会用到的代码东东。

好了,到现在,检测内存泄露总算完成了,而且他还能定位到到底是代码中哪个文件,哪行出现了内存泄露。

_CrtSetBreakAlloc(…)来检测内存泄漏

Sets a breakpoint on a specified object allocation order number (debug version only). long _CrtSetBreakAlloc(
   long lBreakAlloc
);

Parameters
lBreakAlloc
Allocation order number, for which to set the breakpoint.
 
Return Value
Returns the previous object allocation order number that had a breakpoint set.

具体用法

1. debug应用程序MyApp

2.在退出app的时候通常会看到output window中提示这样的信息:

Detected memory leaks!

Dumping objects ->

{523983} normal block at 0x082D54D8, 16 bytes long.

Data: <Folder_Root > 46 6F 6C 64 65 72 5F 52 6F 6F 74 00 00 00 00 00

f:\sp\vctools\vc7libs\ship\atlmfc\src\mfc\plex.cpp(29) : {459891} normal block at 0x0852E340, 164 bytes long.

Data: < #S D t$S > E0 23 53 08 44 04 00 00 01 00 00 00 74 24 53 08

把它们copy 保存起来。  其中位于大括号中的数字表明这是本程序第几次分配内存,但是到程序结束却没有释放掉这一块内存,后面还有信息说明这一次内存泄漏共有到少内存。

3. 再次启动做相同的动作后退出app,查看output window类似的内存泄漏提示信息,   找出和上一次相同的数字,以523983为例。然后在文件添加以下代码:

BOOL MyApp::InitInstance()
{
  _CrtSetBreakAlloc(523983);
 ...
4.重新debug app,程序就会自动的停在内存泄漏发生的地方,此时就可以借助Call Stack window定位到真正引起内存泄漏的地方

转载于:https://www.cnblogs.com/YaoHearthStone/p/3630616.html

相关文章:

Java 学习笔记(4)——java 常见类

上次提前说了java中的面向对象&#xff0c;主要是为了使用这些常见类做打算&#xff0c;毕竟Java中一切都是对象&#xff0c;要使用一些系统提供的功能必须得通过类对象调用方法。其实Java相比于C来说强大的另一个原因是Java中提供了大量可用的标准库 字符串 字符串可以说是任何…

浅谈GCC预编译头技术

浅谈GCC预编译头技术 文/jorge ——谨以此文&#xff0c;悼念我等待MinGW编译时逝去的那些时间。 其 实刚开始编程的时候&#xff0c;我是丝毫不重视编译速度之类的问题的&#xff0c;原因很简单&#xff0c;因为那时我用BASICA。后来一直用到C Builder&#xff0c;尽管Borl…

Java项目:慢病报销管理信息系统(java+MySQL+Jdbc+Servlet+Jsp)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能&#xff1a; 慢病管理&#xff0c;医疗机构管理&#xff0c;家庭管理&#xff0c;费用交纳&#xff0c;费用报销&#xff0c;报表统计等等功能。 二、项目运行 环境配置&#xff1a; Jd…

Jetty Cross Origin Filter解决jQuery Ajax跨域访问的方法

当使用jQuery Ajax post请求时可能会遇到类似这样的错误提示 XMLHttpRequest cannot load http://xxxxxx. Origin http://xxxxxx is not allowed by Access-Control-Allow-Origin. 这是Ajax跨域访问权限的问题&#xff0c;服务器端不接受来自另一个不同IP地址的由脚本文件发出的…

php 遍历所有的文件

<?php prin_r(glob($path)); 2 转载于:https://www.cnblogs.com/zqk8553/p/3640071.html

Oracle数据库物理存储结构管理

1、实验目的 &#xff08;1&#xff09;掌握Oracle数据库数据文件的管理。 &#xff08;2&#xff09;掌握Oracle数据库控制文件的管理。 &#xff08;3&#xff09;掌握Oracle数据库重做日志文件的管理。 &#xff08;4&#xff09;掌握Oracle数据库归档管理。 2、实验环境 Wi…

KDE与GNOME的战争史(转载)

虽然在商业方面存在竞争&#xff0c;GNOME与KDE两大阵营的开发者关系并没有变得更糟&#xff0c;相反他们都意识到支持对方的重要性—如果KDE和GNOME无法实现应用程序的共享&#xff0c;那不仅是巨大的资源浪费&#xff0c;而且将导致Linux出现根本上的分裂。 KDE与GNOME是…

[ActionScript 3.0] AS向php发送二进制数据方法之——在URLRequest中构造HTTP协议发送数据...

主类 HTTPSendPHP.as 1 package 2 {3 import com.JPEGEncoder.JPGEncoder;4 import com.fylib.httpRequest.HttpRequestBuilder;5 import com.fylib.httpRequest.HttpRequestBuilderConsts;6 import flash.display.Bitmap;7 import flash.display.BitmapDa…

Java项目:校园招聘平台系统(java+MySQL+Jdbc+Servlet+SpringMvc+Jsp)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能&#xff1a; 用户和企业用户的注册登录&#xff0c;简历的筛选查看搜索&#xff0c;应聘信息互动等等。 二、项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 mysql Eclispe&#xf…

静态资源(StaticResource)和动态资源(DynamicResource)

静态资源&#xff08;StaticResource&#xff09;和动态资源&#xff08;DynamicResource&#xff09; 资源可以作为静态资源或动态资源进行引用。这是通过使用 StaticResource 标记扩展或 DynamicResource 标记扩展完成的。 StaticResource 通过替换已定义资源的值来为 XAML 属…

如何在 Kaggle 首战中进入前 10%(转)

如何在 Kaggle 首战中进入前 10%&#xff08;转&#xff09; 来源&#xff1a;https://dnc1994.com/2016/04/rank-10-percent-in-first-kaggle-competition/ Introduction 本文采用署名 - 非商业性使用 - 禁止演绎 3.0 中国大陆许可协议进行许可。著作权由章凌豪所有。 Kaggle …

一.Timesten安装

一&#xff0c;安装timesten IMDB并测试 1. 创建数据库相关用户和组 groupadd timestenuseradd -g timesten -G dba timestenpasswd timesten2. 创建相关目录 mkdir /etc/TimesTenchmod 775 /etc/TimesTenchown timesten:timesten /etc/TimesTenmkdir /u01/app/timesTen chmod …

linux 入门-1

刚开始接触linux&#xff0c;总有些简单的问题不知道怎么搞定&#xff0c;先将目前汇总的解决方法叫做"linux入门&#xff0d;1",后续在使用过程中逐步总结。 1. 连接 ADSL &#xff1a; sudo pon dsl-provider 断开 ADSL&#xff1a; sudo poff 查看 ADSL 状态&a…

Java项目:家庭理财系统(java+SSM+JSP+Tomcat8+Mysql)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能&#xff1a;家庭理财&#xff0c;财务分析&#xff0c;统计等等。 二、项目运行 运行环境: jdk8tomcat8mysqlIntelliJ IDEA&#xff08; Eclispe , MyEclispe ,Sts 都支持&#xff0c;代…

ORA-19809: 超出了恢复文件数的限制

实验环境&#xff1a;Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod 实验背景&#xff1a;向tough.t中插入40万条记录&#xff0c;然后rollback&#xff0c;接着执行了shutdown abort命令。当重新启动数据库的时候遇到以下问题。 SQL> alter database …

VC manifest

manifest原理和用途 dll是被动态调用的&#xff0c;所以会被若干个程序共享使用的 但是如果dll在应用程序不知道的情况下升级了、或是被另一个程序更改了&#xff0c;就可能会出现问题&#xff0c;即”DLL Hell” 随着系统资源越来越丰富&#xff0c;硬盘不那么紧张&#xff0…

判断年月日是否正确

//输入年月日 Console.Write("请输入年&#xff1a;"); int year Convert.ToInt32(Console.ReadLine()); Console.Write("请输入月&#xff1a;"); int month Convert.ToInt32(Console.ReadLine()); Console.Write("请输入日&#xff1a;"); i…

Java项目:农业计算工具(java+swing)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 1、换算:吨、千克、斤&#xff0c;晌/公顷、亩、平方米&#xff0c;晌/株、亩/株、平方米/株 2、籽粒干重、果穗干重、出籽率计算 3、发芽种粒数、供试种粒数、发芽率计算 4、种子袋、晌、亩、品种 换算 pac…

web网站加速之CDN(Content Delivery Network)技术原理

2019独角兽企业重金招聘Python工程师标准>>> 在不同地域的用户访问网站的响应速度存在差异,为了提高用户访问的响应速度、优化现有Internet中信息的流动,需要在用户和服务器间加入中间层CDN. 使用户能以最快的速度&#xff0c;从最接近用户的地方获得所需的信息&…

response.getWriter().write()和 response.getWriter().print()的区别

异步上传图片的代码。发现里面用了response.getWriter().print(),故联想到response.getWriter().writer&#xff08;&#xff09;,经过一番api的查找与实操&#xff0c;总结如下&#xff1a; response.getWriter()返回的是PrintWriter&#xff0c;这是一个打印输出流。response…

c语言中volatile关键字的作用

读文章之前 可以先看一下《程序员的自我修养 》第28页 过度优化。 volatile 提醒编译器它后面所定义的变量随时都有可能改变&#xff0c;因此编译后的程序每次需要存储或读取这个变量的时候&#xff0c;都会直接从变量地址中读取数据。如果没有 volatile关键字&#xff0c;则编…

Java项目:抽奖点名神器(HTML+可自定义抽选)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 用于年终抽奖或随机点名神器 //获取页面元素var student_box document.getElementById("student_box");//循环生成HTMLvar html "";for (var i 0 ; i < 22; i ) {html <div st…

Hive Metastore 连接报错

背景 项目中需要通过一些自定义的组件来操控hive的元数据&#xff0c;于是使用了remote方式来存储hive元数据&#xff0c;使用一个服务后台作为gateway&#xff0c;由它来控制hive元数据。 现象 在windows上连接hive metastore的时候&#xff0c;无端的会报NullPointerExceptio…

普通的简单对话框

2019独角兽企业重金招聘Python工程师标准>>> activity_main.xml <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"android:layout_width"fill_parent"and…

[POI2007]ZAP-Queries

出处&#xff1a;http://www.cnblogs.com/peng-ym/p/8660937.html &#xff08;还是 建议 去看 原文&#xff09; 题目&#xff1a;链接&#xff1a;https://www.luogu.org/problemnew/show/P3455 #include<bits/stdc.h> #define LL long long #define ULL unsigned long…

python pdb 基础调试

当手边没有IDE,面对着python调试犯愁时&#xff0c;你就可以参考下本文&#xff1b;&#xff08;pdb 命令调试&#xff09;参考&#xff1a;http://docs.python.org/library/pdb.html和 (pdb)help首先你选择运行的 pypython -m pdb myscript.py(Pdb) 会自动停在第一行&#xff…

Java项目:设计管理系统(java+SSM+JSP+MYSQL+layui+Maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能包括&#xff1a; 课题管理&#xff0c;学生管理&#xff0c;内容管理&#xff0c;文件管理&#xff0c;提问管理&#xff0c;教师管理&#xff0c;进度管理等等。 二、项目运行 环境配置…

linux(以ubuntu为例)下Android利用ant自动编译、修改配置文件、批量多渠道,打包生成apk文件...

原创&#xff0c;转载请注明&#xff1a;http://www.cnblogs.com/ycxyyzw/p/4555328.html 之前写过一篇《windows下Android利用ant自动编译、修改配置文件、批量多渠道&#xff0c;打包生成apk文件》&#xff0c;关于ant打包原理&#xff0c;请先阅读此篇文章&#xff0c;再阅…

自制的一个eclipse 插件jcodesmith(代码生成器)

为什么80%的码农都做不了架构师&#xff1f;>>> 实现思路 使用模板输出&#xff0c;定制一些特定的输入【如数据库元素、普通文件】等控制模板生成&#xff1b;并把指定输入可视化&#xff0c;方便使用&#xff1b; 下载安装 插件下载&#xff1a;http://git.os…

Boring counting HDU - 3518 (后缀数组)

Boring counting \[ Time Limit: 1000 ms \quad Memory Limit: 32768 kB \] 题意 给出一个字符串&#xff0c;求出其中出现两次及以上的子串个数&#xff0c;要求子串之间不可以重合。 思路 对字符串后缀数组&#xff0c;然后枚举子串长度 \(len\)&#xff0c;若某一段连续的 \…