////////////////////////////////////-----------test.cpp
//包含文件不能变顺序
#define _CRTDBG_MAP_ALLOC#include <stdlib.h>
#include <crtdbg.h>
#define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
int main()
{
int* leak = new int[10];
//......//
_CrtDumpMemoryLeaks();//这一句一般放最后
}
//////////////////////////////////////////////////////////////////////////////////////////
以下资料收整理于网络
___________________
BoundsChecker工具能检测的错误包括:
(1)指针操作和内存、资源泄漏错误 比如:内存泄露;资源泄漏;对指针的错误操作。
(2)内存操作方面的错误 比如:内存读、写溢出;使用为初始化的内存。
(3)API函数使用错误
______________________
动态分配、回收内存是C/C++编程语言一个最强的特点,但是中国哲学家孙(Sun Tzu,我不知道是谁?那位知道?)指出,最强的同时也是最弱的。这句话对C/C++应用来说非常正确,在内存处理出错的地方通常就是BUGS产生的地方。一个最敏感和难检测的BUG就是内存泄漏-没有把前边分配的内存成功释放,一个小的内存泄漏可能不需要太注意,但是程序泄漏大块内存,或者渐增式的泄漏内存可能引起的现象是:先是性能低下,再就是引起复杂的内存耗尽错误。最坏的是,一个内存泄漏程序可能用完了如此多的内存以至于引起其他的程序出错,留给用户的是不能知道错误到底来自哪里。另外,一个看上去无害的内存泄漏可能是另一个问题的先兆。幸运的是VC++DEBUGER和CRT库提供了一组有效的检测和定位内存泄漏的工具。本文描述如何使用这些工具有效和系统的排除内存泄漏。
_CrtDumpMemoryLeaks()就是检测从程序开始到执行该函数进程的堆使用情况,通过使用_CrtDumpMemoryLeaks()我们可以进行简单的内存泄露检测。
启用内存泄露检测:
检测内存泄漏的主要工具是调试器和 C 运行时库 (CRT) 调试堆函数。若要启用调试堆函数,请在程序中包括以下语句:
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
注意:
#include必须按照以上所示顺序。如果更改了顺序,所使用的函数可能无法使用。
通过包括crtdbg.h,将 malloc 和 free 函数映射到其“Debug”版本 _malloc_dbg 和 _free_dbg,这些函数将跟踪内存分配和释放。此映射只在调试版本(在其中定义了 _DEBUG)中发生。发布版本使用普通的 malloc 和 free 函数。
#define 语句将 CRT 堆函数的基版本映射到对应的“Debug”版本。并非绝对需要该语句,但如果没有该语句,内存泄漏转储包含的有用信息将较少。
在添加了上面所示语句之后,可以通过在程序中包括以下语句来转储内存泄漏信息:
_CrtDumpMemoryLeaks()
使用示范:
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
int main()
{
int* leak = new int[10];
_CrtDumpMemoryLeaks();
}
这个示范程序与前面讲的多了一个宏定义:
#define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
原因稍后再说,我们先看看程序运行(提醒:不要按Ctrl+F5,按F5)后的结果。
程序调试后在“输出”窗口输出如下:
Detected memory leaks!
Dumping objects ->
e:workmyprojecttesttesttest.cpp(57) : {48} client block at 0x00392BB0, subtype 0, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
“输出”很明显的告诉了你在test.cpp文件的第57行分配了一个40字节的内存而没有释放。在“输出”窗口中选择包含文件名和行号的行,然后按 F4 键即可进入到源文件中分配内存的行。
现在我们再来看看如果不加之前那个new的宏定义会出现怎么样的结果。
程序调试后在“输出”窗口输出如下:
Detected memory leaks!
Dumping objects ->
{48} normal block at 0x00392BB0, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
与之前的对比,这次的输出并没有告诉我们这个内存泄露具体是在哪个位置引起的。另外如果没定义
#define _CRTDBG_MAP_ALLOC
也会引起同样的结果。
_______________________________________________________________________________________________________
http://blog.youkuaiyun.com/xiaoakang/article/details/1503360
VC++中本身就有内存泄漏检查的机制,可以在向导生成的支持MFC的工程中看到如下代码:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
先具体解释一下:
#ifdef _DEBUG //如果有定义_DEBUG
#define new DEBUG_NEW //将new宏定义成DEBUG_NEW,
那么则是代码中有new的都换成DEBUG_NEW。
#undef THIS_FILE//取消THIS_FILE的宏定义
static char THIS_FILE[] = __FILE__;//将THIS_FILE
定义成一个数组,该数组用static声名,则只能在该文件内访问。__FILE__是gcc定义的一个扩展宏,代表的该文件的文件名。
#endif//这个不用说了吧
MFC Library中的解释
THIS_FILESee Also
MFC Macros and Globals | ASSERT | VERIFYExpands to the name of the file that is being compiled.
THIS_FILE
Remarks
The information is used by the ASSERT and VERIFY macros. The Application Wizard and code wizards place the macro in source code files they create.
Example
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// __FILE__ is one of the six predefined ANSI C macros that the
// compiler recognizes.
有了这样的定义,在编译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个字符串都是这个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 );
例如:
#include "stdafx.h"
#include <crtdbg.h>
int main(int argc, char* argv[])
{
int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
_CrtSetDbgFlag( tmpFlag );
printf("Hello World!\n");
char *aa = new char[100];
return 0;
}
这样,在程序结束的时候,也就是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的泄漏,它是无法检测到的,这是这种检测法的一个重大的局限性。另外,为了能记录内存块是在哪里分配的,源代码必须相应的配合,这在调试一些老的程序非常麻烦,毕竟修改源代码不是一件省心的事,这是这种检测法的另一个局限性。
对于开发一个大型的程序,MS C-Runtime Library提供的检测功能是远远不够的。接下来我们就看看外挂式的检测工具。我用的比较多的是BoundsChecker,一则因为它的功能比较全面,更重要的是它的稳定性。这类工具如果不稳定,反而会忙里添乱。到底是出自鼎鼎大名的NuMega,我用下来基本上没有什么大问题。