2007年9月25日星期二

VC++6.0中内存泄漏检测

这篇文章是对2004-09-02日发表的《VC++6.0中简单的内存泄漏检测事例代码》(已经删除)的更新.

对C++代码而言,内存泄漏问题虽然有诸多方法避免,但实际代码编写的时候,或出于自信或出于复杂性的考虑,常常还会用到原始的operator new,这不可避免的会带来内存泄漏的可能,不久前本人因为违反了"可用于被多态继承的基类其析构函数应当有virtual修饰"的法则( 一不小心就忘了写virtual ^_^ ),导致了内存泄漏,因此我觉得出于安全考虑,在代码中加入内存泄漏检查机制还是很必要的,也因为这次的内存泄漏事件促使我写出这一篇文章.

VC++中本身就有内存泄漏检查的机制,你可以在向导生成的支持MFC的工程中看到如下代码:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
通过它们,你能非常容易的发现代码中的内存泄漏,但是如果手工将这个功能移植到非MFC工程中去是很繁琐的一件事,另外它还有一个bug,在多线程并发调 用这个DEBUG_NEW时会导致系统级错误,因此本人在此重写了这个功能,将以下的debug_new.h和debug_new.cpp添加到工程中, 并在需要检测的cpp中#include "debug_new.h"和main中一开始处加入REG_DEBUG_NEW宏即可.

1. debug_new.h 源代码
/************************************************************************/
/* comment: 此文件与debug_new.cpp配合使用,用于在调试期发现内存泄漏 */
/* 仅在VC++编译器中适用(包括Intel C++,因为它使用了相同的库) */
/* 作者: 周星星 http://blog.vckbase.com/bruceteen/ */
/* 版权申明: 无,可任意 使用,修改 和 发布 */
/************************************************************************/

/* sample

#include
#include "debug_new.h" // +
using namespace std;

int main( void )
{
REG_DEBUG_NEW; // +

char* p = new char[2];

cout << "--End--" << endl;
return 0;
}

在VC++ IDE中按F5调试运行将会在Output窗口的Debug页看到类似如下的提示:
Dumping objects ->
d:\test.cpp(10) : {45} normal block at 0x003410C8, 2 bytes long.
Data: < > CD CD
Object dump complete.

如果不出现如上提示请Rebuild All一次.

*/


#ifndef _DEBUG_NEW_H_
#define _DEBUG_NEW_H_

#ifdef _DEBUG

#undef new
extern void _RegDebugNew( void );
extern void* __cdecl operator new( size_t, const char*, int );
extern void __cdecl operator delete( void*, const char*, int);
#define new new(__FILE__, __LINE__)

#define REG_DEBUG_NEW _RegDebugNew();

#else

#define REG_DEBUG_NEW

#endif // _DEBUG

#endif // _DEBUG_NEW_H_


2. debug_new.cpp 源代码
/************************************************************************/
/* comment: 此文件与debug_new.h配合使用,用于在调试期发现内存泄漏 */
/* 仅在VC++编译器中适用(包括Intel C++,因为它使用了相同的库) */
/* 作者: 周星星 http://blog.vckbase.com/bruceteen/ */
/* 版权申明: 无,可任意 使用,修改 和 发布 */
/************************************************************************/

//#include "debug_new.h"

#ifdef _DEBUG

#include
#include

class _CriSec
{
CRITICAL_SECTION criSection;
public:
_CriSec() { InitializeCriticalSection( &criSection ); }
~_CriSec() { DeleteCriticalSection( &criSection ); }
void Enter() { EnterCriticalSection( &criSection ); }
void Leave() { LeaveCriticalSection( &criSection ); }
} _cs;

void _RegDebugNew( void )
{
_CrtSetDbgFlag( _CRTDBG_REPORT_FLAG | _CRTDBG_LEAK_CHECK_DF );
}
void* __cdecl operator new( size_t nSize, const char* lpszFileName, int nLine )
{
// comment 1: MFC中提供的debug new虽然加了锁,但我在实际测试的时候发现多线程并发
// 调用的时候还是抛出了系统错误,所以我在这里加了一个线程互斥量.
// comment 2: debug new和debug delete之间需不需要互斥我并不知道,保险起见,我同样
// 加了线程互斥量.
// comment 3: 按照C++标准规定,在operator new失败后应当调用set_new_handler设置的
// 函数,但是MSDN中却说"头文件new中的set_new_handler是stub的,而应该使
// 用头文件new.h中的_set_new_handler",这简直是滑天下之大稽.
// 以下是VC++6.0中的set_new_handler定义:
// new_handler __cdecl set_new_handler( new_handler new_p )
// {
// assert( new_p == 0 ); // cannot use stub to register a new handler
// _set_new_handler( 0 );
// return 0;
// }
// 所以我也无计可施,只能舍弃set_new_handler的作用.

_cs.Enter();
void* p = _malloc_dbg( nSize, _NORMAL_BLOCK, lpszFileName, nLine );
_cs.Leave();
return p;
}
void __cdecl operator delete( void* p, const char* /*lpszFileName*/, int /*nLine*/ )
{
_cs.Enter();
_free_dbg( p, _CLIENT_BLOCK );
_cs.Leave();
}

#endif


3. 事例代码
#include
#include "debug_new.h"
using namespace std;

int main( void )
{
REG_DEBUG_NEW;

char* p = new char[2];
p[0] = 'A';
p[1] = 'B';

cout << "--End--" << endl;
return 0;
}


4. 结果输出
在VC++ IDE中按F5调试运行将会在Output窗口的Debug页看到类似如下的提示:
……
Dumping objects ->
d:\test.cpp(10) : {45} normal block at 0x003410C8, 2 bytes long.
Data: 41 42
Object dump complete.
……

posted on 2004-10-28 04:49 周星星 阅读(28808) 评论(28) 编辑 收藏

评论

# 好帖! 2004-10-28 05:01 一笑
收藏ing...

# re: VC++6.0中内存泄漏检测 2004-10-30 01:26 lxwde
我也支持一下楼主

# re: VC++6.0中内存泄漏检测 2004-11-01 10:28 zhuyie
默认显示的字体好像太小了,呵呵;


# to zhuyie: 2004-11-01 21:40 周星星
不知道为什么字体和大小这几天变了,而且不受我控制,以前还好好的。

# re: VC++6.0中内存泄漏检测 2004-11-03 09:39 soloist
对"头文件new中的set_new_handler是stub的"这句话中"stub"的意思我有点不太明白,是不是指set_new_handler 本身并没有实现任何功能,只是提供了一个接口,真正功能的完成靠的是在内部调用_set_new_handler?
另外,在实际开发的时候_CrtSetDbgFlag这个函数应该越早调用越好,因为C++中全局对象的初始化是在main函数之前,而在它们的构造函数 里极有可能做了分配堆内存的动作,如果我们仅仅在刚进入main的时候调用_CrtSetDbgFlag,则无法检测到有可能由上述全局对象引起的内存泄 漏。我的建议是定义一个类,在它的构造函数里调用_CrtSetDbgFlag,然后在main函数所在的CPP里首先定义一个上述类的全局对象。当然, C++标准不保证全局变量初始化的顺序,所以先于它的全局变量初始化引发的堆内存分配仍然不受监控,但是这个方法毕竟能扩大一点监控范围,对吧?
一个问题和一个建议,还请周星星兄指正。


# to soloist: 2004-11-03 22:03 周星星
VC++6.0中的set_new_handler:
/***
* WARNING: set_new_handler is a stub function that is provided to
* allow compilation of the Standard Template Library (STL).
* Do NOT use it to register a new handler. Use _set_new_handler instead.
* However, it can be called to remove the current handler:
* set_new_handler(NULL); // calls _set_new_handler(NULL)
*******************************/
new_handler __cdecl set_new_handler (new_handler new_p)
{
// cannot use stub to register a new handler
assert(new_p == 0);

// remove current handler
_set_new_handler(0);

return 0;
}


对于第二点非常感谢,我当初甚至想使用
#pragma comment(linker, "/ENTRY:Inti")
void __cdecl Init( void )
{
……
}
来让它一开始就起作用,但……^_^,不会,免得用法弄错了,遗笑大方,要不你帮我改改?:)


# re: 周星星 2004-11-04 10:48 soloist
用#pragma comment(linker, "/ENTRY:Init"),我也试了一下。如果在Init中做完了指定的动作后直接调用main或者WinMain的话我觉得不太安全,因为通过观察 代码发现在诸如mainCRTStartup这样的默认入口函数中系统做了许多检查和初始化的工作(好象挺复杂,我也没看懂)。但是如果要显式地调用它又 得导入不少VC的内部文件(而且我也没试成功,如果你能成功调用的话,烦请告诉方法与具体设置),将来用新的开发工具会否出问题就未可知了。
我觉得我上次提的建议也不很好,还不如直接在你的operator new的入口处加上对_CrtSetDbgFlag的调用,反正它也几乎不耗什么时间。这样不管任何地方用到new都能保证DbgFlag标志已经正确设 置了。呵呵,我实在想不到其它的更好方法了^_^。
另外还建议(为什么我的话这么多?)将你的_CriSec _cs全局对象定义改成:

_CriSec& _cs()
{
static _CriSec s_cs;

return s_cs;
};

而所有用到_cs的地方改成_cs()。因为全局对象初始化顺序不能确定,所以有可能当其它全局变量在初始化中调用new时_cs本身还没有被初始化。用这种函数的形式可以保证任何时候通过_cs()得到的是一个被正确初始化的_CriSec对象的引用。

# to soloist: 2004-11-04 21:51 周星星
谢谢你的热心指正,两个观点都非常正确,我有空就改掉它。

# 很困惑,希望得到指点一下 2004-11-09 07:47 笑柄
1.新建win32 console application 取名字test
2.new file ->c++ header debug_new.h,copy...;
3.new file ->c++ source debug_new.cpp,copy...;
4.new file ->c++ source test.cpp,copy...;
5.rebuild all
6.f5
出来下面这个,我是VC6.0+win2003s,好象都没打补丁
Loaded 'ntdll.dll', no matching symbolic information found.
Loaded 'C:\WINDOWS\system32\kernel32.dll', no matching symbolic information found.
The thread 0x784 has exited with code 0 (0x0).
The program 'E:\MYPROJECT\test\Debug\test.exe' has exited with code 0 (0x0).


# to 笑柄: 2004-11-09 21:39 周星星
我没有win2003s,等我有了,我测试一下。谢谢你告诉我这个错误!

# VC 6.0中内存泄漏检测[TrackBack] 2004-11-09 09:05 NetSniffer
Ping Back来自:blog.csdn.net
NetSniffer引用了该文章,地址:http://blog.csdn.net/netsniffer/archive/2004/11/09/173437.aspx

# re: VC++6.0中内存泄漏检测 2004-11-19 05:43 kevie221@hotmail.com
我跟笑柄得到差不多的结果,我是2000+vc6.0,结果如下:
Loaded 'C:\WINNT\system32\ntdll.dll', no matching symbolic information found.
Loaded 'C:\WINNT\system32\KERNEL32.DLL', no matching symbolic information found.
Loaded 'C:\Program Files\rising\rav\ApiHook.dll', no matching symbolic information found.
Loaded 'C:\WINNT\system32\ADVAPI32.DLL', no matching symbolic information found.
Loaded 'C:\WINNT\system32\rpcrt4.dll', no matching symbolic information found.
Loaded 'C:\Program Files\rising\rav\Memmon.dll', no matching symbolic information found.
Loaded 'C:\WINNT\system32\USER32.DLL', no matching symbolic information found.
Loaded 'C:\WINNT\system32\GDI32.DLL', no matching symbolic information found.
Loaded 'C:\WINNT\system32\imm32.dll', no matching symbolic information found.
Loaded 'C:\WINNT\system32\lpk.dll', no matching symbolic information found.
Loaded 'C:\WINNT\system32\usp10.dll', no matching symbolic information found.
The thread 0x248 has exited with code 0 (0x0).
The thread 0x4D0 has exited with code 0 (0x0).
The program 'C:\PROGRAM FILES\MICROSOFT VISUAL STUDIO\MYPROJECTS\debug_new\Debug\debug_new.exe' has exited with code 0 (0x0).
请问期望的结果为什么没有出现?盼望答复

# re: VC++6.0中内存泄漏检测 2004-11-23 22:01 竹叶
--------------------Configuration: test - Win32 Debug--------------------
Compiling...
test.cpp
debug_new.cpp
Linking...
LIBCD.lib(wincrt0.obj) : error LNK2001: unresolved external symbol _WinMain@16
Debug/test.exe : fatal error LNK1120: 1 unresolved externals
Error executing link.exe.

test.exe - 2 error(s), 0 warning(s) 这是为何?

# re: VC++6.0中内存泄漏检测 2004-12-24 05:02 JerryZ
  很多人运行**的例子代码都没有得到预期的结果,我也是,我看了 MSDN 相关资料后明白了,也许是**留了一手,下面我来揭开谜底,呵呵:

1、检测内存泄漏的基本工具是调试器和 CRT 调试堆函数。为了使用调试堆函数,必须在要调试的程序中添加下面的语句:

#define _CRTDBG_MAP_ALLOC
#include
#include

  必须保证上面声明的顺序,如果改变了顺序,可能不能正常工作。的_malloc_dbg和 _free_dbg将取代标准的malloc和free函数出现在DEBUG版中,它可以跟踪内存的分配和释放。但是这只会在DEBUG版本中发生(当 #define _DEBUG的时候),而Release版本仍使用标准的malloc和free功能。
  #define _CRTDBG_MAP_ALLOC表示使用CRT堆函数的相应的DEBUG版本。这个定义不是必须的,但是没有它,内存泄漏报告中的信息不是很详细。

2、一旦你已经添加了刚才的声明,你就能够通过在程序中加入下面的代码来报告内存泄漏信息:

_CrtDumpMemoryLeaks();

当在DEBUG模式下运行程序时,在Output窗口的Debug页会显示内存泄漏的信息,例如:

Detected memory leaks!
Dumping objects ->
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {45} normal block at 0x00441BA0, 2 bytes long.
Data: 41 42
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {44} normal block at 0x00441BD0, 33 bytes long.
Data: <> 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {43} normal block at 0x00441C20, 40 bytes long.
Data: <> E8 01 43 00 16 00 00 00 00 00 00 00 00 00 00 00
Object dump complete.

MSDN 中讲得非常详细,有什么问题请大家多看多读,肯定会有收获。不啰嗦了。祝大家圣诞快乐!

# re: VC++6.0中内存泄漏检测 2005-01-18 06:56 meteor135
我觉得只有这两个文件就够了,test.cpp中可以不显式出现任何处理动作,而由debug_new.cpp中的全局类变量的构造和析构过程完成。这样就摆脱了应用工程和调试工具的耦合。不知道大家对此有什么看法请评论。

注:工程中只需要加入debug_new.cpp

///////////////////////////////////////////////////
// test.cpp

#include
using namespace std;

int main( void )
{
char* p = new char[2];
p[0] = 'A';
p[1] = 'B';

cout << "--End--" << endl;

return 0;
}

///////////////////////////////////////////////////
//debug_new.cpp

#ifdef _DEBUG
#include
#include

class _DebugUtil
{
public:
_DebugUtil()
{
_CrtSetDbgFlag( _CRTDBG_REPORT_FLAG | _CRTDBG_LEAK_CHECK_DF );
}
~_DebugUtil()
{
_CrtDumpMemoryLeaks();
}
} _debugUtil;

class _CriSec
{
CRITICAL_SECTION criSection;
public:
_CriSec()
{
InitializeCriticalSection( &criSection );
}
~_CriSec()
{
DeleteCriticalSection( &criSection );
}
void Enter() { EnterCriticalSection( &criSection ); }
void Leave() { LeaveCriticalSection( &criSection ); }
} _cs;

void _RegDebugNew( void )
{
_CrtSetDbgFlag( _CRTDBG_REPORT_FLAG | _CRTDBG_LEAK_CHECK_DF );
}
void* __cdecl operator new( size_t nSize, const char* lpszFileName, int nLine )
{
_cs.Enter();
void* p = _malloc_dbg( nSize, _NORMAL_BLOCK, lpszFileName, nLine );
_cs.Leave();
return p;
}
void __cdecl operator delete( void* p, const char* /*lpszFileName*/, int /*nLine*/ )
{
_cs.Enter();
_free_dbg( p, _CLIENT_BLOCK );
_cs.Leave();
}

#endif


# re: VC++6.0中内存泄漏检测 2005-01-18 06:58 meteor135
上面的
void _RegDebugNew( void )
{
_CrtSetDbgFlag( _CRTDBG_REPORT_FLAG | _CRTDBG_LEAK_CHECK_DF );
}

也不需要了。

# to meteor135: 2005-01-18 21:56 周星星
谢谢!

# re: VC++6.0中内存泄漏检测 2005-01-21 11:13 lift

#ifndef _DEBUG_NEW_H_
#define _DEBUG_NEW_H_

#ifdef _DEBUG

#ifdef new
#undef new
#endif

#define CRTDBG_MAP_ALLOC
#include
#include

#include

namespace __impl {
class CCriticalSection {
public:
CCriticalSection() { ::InitializeCriticalSection( &m_cs ); }
~CCriticalSection() { ::DeleteCriticalSection( &m_cs ); }
void Enter() { ::EnterCriticalSection( &m_cs ); }
void Leave() { ::LeaveCriticalSection( &m_cs ); }
private:
CRITICAL_SECTION m_cs;
};

class CDumbCS {
public:
void Enter() { }
void Leave() { }
};

class CGuard {
public:
#ifdef _MT
typedef CCriticalSection lock_type;
#else
typedef CDumbCS lock_type;
#endif

static lock_type* GetLock() {
static lock_type lock;
return &lock;
}

public:
CGuard() { GetLock()->Enter(); }
~CGuard() { GetLock()->Leave(); }
};

class CDebugEnv {
public:
CDebugEnv() {
::_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
}
};
}

// normal
inline
void* __cdecl operator new( size_t nSize, const char* lpszFileName, int nLine )
{
static __impl::CDebugEnv __dbgEnv;
__impl::CGuard guard;
return ::_malloc_dbg( nSize, _NORMAL_BLOCK, lpszFileName, nLine );
}

// 当"new表达式调用的构造函数抛出异常"时可以正确地释放内存
inline
void __cdecl operator delete( void* p, const char* /*lpszFileName*/, int /*nLine*/ )
{
__impl::CGuard guard;
::_free_dbg( p, _NORMAL_BLOCK );
}

// normal
inline
void __cdecl operator delete( void* p)
{
__impl::CGuard guard;
::_free_dbg( p, _NORMAL_BLOCK );
}

#define new new(__FILE__, __LINE__)

#endif // _DEBUG


#endif


# re: VC++6.0中内存泄漏检测 2005-01-21 11:22 lift
作为一个头文件包含就可以了,不需cpp文件
优化了单线程时的情况
全局对象初始化的问题已经考虑进去了
必须提供两个delete, 一个在new对象而构造函数抛出异常时由系统自动调用,一个为用户delete时调用


# re: VC++6.0中内存泄漏检测 2005-02-12 22:07 achun
MS的crtdbg.h 真是莫名其妙!明明是
#ifndef _DEBUG
.....
#define _CrtSetDbgFlag(f) ((int)0)
那么在debug mode下应该无法使用_CrtSetDbgFlag的,他怎么用的呀?毫无道理!

# re: VC++6.0中内存泄漏检测 2007-06-05 16:42 Aplia
会和Direct3D中的d3dx9math.h冲突

# re: VC++6.0中内存泄漏检测 2007-06-12 10:20 x68251
我看了各位的评论,在我的代码中调试出现了下面的问题,请各位帮忙解决,谢谢!
Dumping objects ->
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\plex.cpp(29) : {213} normal block at 0x04AFBE60, 124 bytes long.
Data: < > 00 00 00 00 00 00 00 00 06 0F 02 00 20 BB AF 04
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\map_pp.cpp(69) : {212} normal block at 0x04AFBDE0, 68 bytes long.
Data: 64 BE AF 04 00 00 00 00 00 00 00 00 00 00 00 00
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\plex.cpp(29) : {211} normal block at 0x04AFBD28, 124 bytes long.
Data: < > 00 00 00 00 00 00 00 00 00 00 00 00 E0 BC AF 04
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\occcont.cpp(337) : {210} normal block at 0x04AFBCE0, 12 bytes long.
Data: < > 00 00 00 00 20 BB AF 04 00 00 00 00
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(781) : {190} client block at 0x04AFBB20, subtype c0, 236 bytes long.
a CCmdTarget object at $04AFBB20, 236 bytes long
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\plex.cpp(29) : {189} normal block at 0x04AFBA68, 124 bytes long.
Data: <> 00 00 00 00 00 00 00 00 00 00 00 00 70 86 AF 04
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(788) : {188} normal block at 0x04AF8670, 4 bytes long.
Data: < : x> DC 3A 1F 78
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(143) : {183} client block at 0x04AFA330, subtype c0, 128 bytes long.
a CCmdTarget object at $04AFA330, 128 bytes long
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\ctlcore.cpp(574) : {179} client block at 0x04AFA0C8, subtype c0, 88 bytes long.
a CWnd object at $04AFA0C8, 88 bytes long
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\ctlview.cpp(239) : {159} normal block at 0x04AF9E30, 12 bytes long.
Data: <> 01 00 00 00 00 00 00 00 4C F6 1D 00
.\WebICTCtrl.cpp(14) : {150} client block at 0x04AF9B68, subtype c0, 652 bytes long.
a WebICTCtrl object at $04AF9B68, 652 bytes long
f:\rtm\vctools\vc7libs\ship\atlmfc\src\mfc\dllmodul.cpp(133) : {146} client block at 0x04AF9AE8, subtype c0, 64 bytes long.
a CDynLinkLibrary object at $04AF9AE8, 64 bytes long
{141} client block at 0x04AF85F0, subtype c0, 64 bytes long.
a CDynLinkLibrary object at $04AF85F0, 64 bytes long
{139} normal block at 0x04AF9808, 52 bytes long.
Data: < > 08 98 AF 04 08 98 AF 04 08 98 AF 04 CD CD CD CD
{63} client block at 0x04AF3218, subtype c0, 64 bytes long.
a CDynLinkLibrary object at $04AF3218, 64 bytes long
Object dump complete.

# re: VC++6.0中内存泄漏检测 2007-06-12 10:24 x68251
忘了说,我没有f盘,搜索也没有找到上面所说的.cpp文件.
怎样用它的内存编号调试???


# re: VC++6.0中内存泄漏检测 2007-07-17 15:56 小字辈
MFC工程的程序 是不是有下面的就行了
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


# re 小字辈: 2007-07-17 18:07 周星星
MFC用向导生成的代码会自己加

# re: VC++6.0中内存泄漏检测 2007-09-14 16:18 JUSTIN
# re: VC++6.0中内存泄漏检测 2004-12-24 05:02 JerryZ
应该不是这个文件有泄露才对 啊,应该是***。CPP

没有评论: