【tools】使用Visual Studio查找和定位内存泄露 @Windows

一,代码示例

 1 #include <stdio.h>
 2 
 3 void* memleak1();
 4 void* memleak2();
 5 
 6 int main()
 7 {
 8     void *p1 = memleak1();
 9     void *p2 = memleak2();
10 
11     printf("p1=%p, p2=%p\n", p1, p2);
12 
13     return 0;
14 }
main.c
1 /* memleak1.c */
2 #include <stdlib.h>
3 
4 void* memleak1()
5 {
6     return malloc(1);
7 }
memleak1.c
1 /* memleak2.c */
2 #include <stdlib.h>
3 
4 void* memleak2()
5 {
6     return malloc(2);
7 }
memleak2.c

二、如何查找是否有内存泄露

第一步:把下面的代码黏贴到main()函数所在的源文件:

#ifdef _DEBUG
 
#include <stdlib.h>
#include <crtdbg.h>
 
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif
 
#endif

第二步,把下面的代码黏贴到main()函数的开头:

char *b;
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
b = (char*) malloc(20);

最后,重新编译debug模式并运行vs工程,在输出窗口就可以看到这样的提示:

Detected memory leaks!
Dumping objects ->
{56} normal block at 0x00171580, 20 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
{55} normal block at 0x00171540, 2 bytes long.
 Data: <  > CD CD 
{54} normal block at 0x00171500, 1 bytes long.
 Data: < > CD 
Object dump complete.

这里,我们就可以看到内存泄露的位置和泄露的数量。

三、如何定位内存泄露的位置

ATTENTION:

    这是整篇博客中最耗时耗力的部分,我们不建议通过这样的办法定位内存泄露,因为你必须把每一个调用了malloc/calloc/realloc的地方都加上下面所说的代码。尤其的,对于第三方库中的泄露,这个办法根本找不出来。我们更推荐读者寻找Windows上的其他替代方案。在stackoverflow上就有相关的讨论。另外,牛逼到没朋友的Valgrind似乎推出了Windows版本,读者可以试一下。

 

第一步,添加预定义宏(preprocessor marco)到vs工程中:

_CRTDBG_MAP_ALLOC=1

这时候重新编译并运行工程,我们会得到这样的输出,说明我们已经定位20个byte的泄露来自于main.c里的第25行,正是我们加入的测试内存泄露的代码。但是我们还是没有定位到另外两处内存泄露。

Detected memory leaks!
Dumping objects ->
c:\users\jxion\documents\github\memleak\memleak\main.c(25) : {56} normal block at 0x00401580, 20 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
{55} normal block at 0x00401540, 2 bytes long.
 Data: <  > CD CD 
{54} normal block at 0x00401500, 1 bytes long.
 Data: < > CD 
Object dump complete.

第二步,在所有我们怀疑有内存泄露的源文件的开始位置,添加下面的代码:

#ifdef _DEBUG
 
#include <stdlib.h>
#include <crtdbg.h>
 
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif
 
#endif

这时我们可以定位所有的内存泄露的位置了:

Detected memory leaks!
Dumping objects ->
c:\users\jxion\documents\github\memleak\memleak\main.c(25) : {56} normal block at 0x000F1580, 20 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
c:\users\jxion\documents\github\memleak\memleak\memleak2.c(19) : {55} normal block at 0x000F1540, 2 bytes long.
 Data: <  > CD CD 
c:\users\jxion\documents\github\memleak\memleak\memleak1.c(19) : {54} normal block at 0x000F1500, 1 bytes long.
 Data: < > CD 
Object dump complete.

四、查找第三方库是否有内存泄露:

调用第三方预编译库(prebuilt)也有可能有内存泄露,但是我们没办法把上面的代码加入到第三方库中重新编译库。我们只能使用间接的方法。例如:打印我们认为可能有内存泄露的第三方函数调用,然后与输出窗口里的内存泄露地址做比较,如上面的例子中,内存泄露位置为:

Detected memory leaks!
Dumping objects ->
c:\users\jxion\documents\github\memleak\memleak\main.c(25) : {56} normal block at 0x002D1580, 20 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
{55} normal block at 0x002D1540, 2 bytes long.
 Data: <  > CD CD 
{54} normal block at 0x002D1500, 1 bytes long.
 Data: < > CD 
Object dump complete.

同时打印的内存分配地址为:

发现了没?两者的地址是一致的,我们同样可以拿这个作为一个定位内存泄露的原则。

五、把内存泄露报告重定向到标准输出(错误)

The memory leak report is printed on Visual Studio Output window. If we run some automatic test, no memory leak report will be printed either in stderr.txt or stdout.txt. So we have to redirect the report to stdout or stderr first.
Add these two functions to the source file where main() locates:

/* Send all memory error reports to stderr instead of the Output window */
static
void
SendReportsToStderr()
{
	_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
    _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
    _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
}

/* Send all memory error reports to stdout instead of the Output window */
static
void
SendReportsToStdout()
{
    _CrtSetReportMode (_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile (_CRT_WARN, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode (_CRT_ERROR, _CRTDBG_MODE_FILE);
    _CrtSetReportFile (_CRT_ERROR, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode (_CRT_ASSERT, _CRTDBG_MODE_FILE);
    _CrtSetReportFile (_CRT_ASSERT, _CRTDBG_FILE_STDOUT);
}

Then invoke SendReportsToStderr() if you need to print the memory leak in stderr.txt, or SendReportsToStdout() if in stdout.txt, for example:

    char *b;
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
SendReportsToStdout(); // redirect memory leak report to stdout
b = (char*) malloc(20);
posted @ 2014-08-23 15:30  xjs_xjtu  阅读(964)  评论(0编辑  收藏  举报