Finding Memory Leaks Using the CRT Library

 

CRT 有一个非常好用的, 检测 mem leak 的子系统, 下面介绍一下它的用法. msdn 官网 doc: http://msdn.microsoft.com/en-us/library/x98tx3cf.aspx (msdn 原文中有一些谬误, 我经过自己的实践发现和其描述不符, 请见下文 'msdn 勘误' 部分)

本文主要介绍了如下技术:

  1. 输出某时间点 mem leak 信息.
  2. 检测某段函数 mem leak 情况.
  3. 程序退出后的 mem leak 检测.

 

预备知识: 

所谓 CRT 检测 mem leak 机制, 就是使用带追踪信息的函数( _malloc_dbg/_free_dbg 以及 new(_CLIENT_BLOCK, __FILE__, __LINE__)/delete )代替传统的函数.  

先看代码中和注释吧:

// DebugMemLeak.h
// Recommand include me in stdafx.h to enable memory check in debug moode. 
// For more details: http://msdn.microsoft.com/en-us/library/x98tx3cf.aspx

#pragma once

#define _CRTDBG_MAP_ALLOC       // When the _CRTDBG_MAP_ALLOC flag is defined in the debug version of an application, 
                                // the base version of the heap functions are directly mapped to their debug versions. 
                                // The flag is used in Crtdbg.h to do the mapping. This flag is only available 
                                // when the _DEBUG flag has been defined in the application.

// For the CRT functions to work correctly, the #include statements must follow the order shown here.
#include <stdlib.h>        
#include <crtdbg.h>             // Including crtdbg.h maps the malloc and the free functions to their debug versions, _malloc_dbg and free, 
                                // which track memory allocation and deallocation. This mapping occurs only in debug builds, which have _DEBUG. 
                                // Release builds use the ordinary malloc and free functions.

#ifdef _DEBUG
    #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) // The #define statement maps a base version of the CRT heap functions
                                                                // to the corresponding debug version. If you omit the #define statement, 
                                                                // the memory leak dump will be less detailed.
    #define new DBG_NEW
#endif  // _DEBUG

 

上述 DebugMemLeak 中的各个部分功能, 注释中已经很详尽了. 其中有几点注意事项:

1. 中间的两个 `include` 顺序不能颠倒.

2. 仅仅在有 _DEBUG 的编译模式下, 这个子系统才工作. 否则, 所有 malloc/free 以及 new/delete 都是不同的, 不带 debug 能力的函数. (在 release 模式下, 即使你手动调用 _malloc_dbg 也将映射到普通的 malloc)

 

基本用法介绍:

1. 输出某时间点 mem leak 信息.

#include "DebugMemLeak.h"

int _tmain(int argc, _TCHAR* argv[])
{
    // Leak!
    auto pI = new int[1];
    pI = nullptr;


    // Dump the memory leak. Result as follow:
    /*************
            Detected memory leaks!
            Dumping objects ->
            e:\work\memleakdemo\memleakdemo.cpp(27) : {120} client block at 0x003DB448, subtype 0, 4 bytes long.
             Data: <    > CD CD CD CD 
            Object dump complete.
    **************/
    _CrtDumpMemoryLeaks();      // After you have enabled the debug heap functions by using these statements, 
                                // you can place a call to _CrtDumpMemoryLeaks before an application exit point 
                                // to display a memory-leak report when your application exits.
                                // By default, _CrtDumpMemoryLeaks outputs the memory-leak report to the Debug pane of 
                                // the Output window. You can use _CrtSetReportMode to redirect the report to another 
                                // location: `_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );`

    return 0;
}

`_CrtDumpMemoryLeaks()` 函数打印当前状态下 heap 的信息:

怎么样, 超级赞吧.

 

2. 检测某段函数 mem leak 情况.

#include "DebugMemLeak.h"

void foo()
{
    new int[1];
}

int _tmain(int argc, _TCHAR* argv [])
{
    _CrtMemState cmsBefore = {}, cmsAfter = {};

    _CrtMemCheckpoint(&cmsBefore);                // Snapshot memory usage before you function.

    // Do what you want.
    // And something else....
    foo();
    
    _CrtMemCheckpoint(&cmsAfter);                // Snapshot memory usage after you function.

    _CrtMemState cmsDiff = {};
    if (_CrtMemDifference(&cmsDiff, &cmsBefore, &cmsAfter))    // Compare the difference.
    {
        _CrtMemDumpStatistics(&cmsDiff);        // Dump the memory leak.
    }

    return 0;
}

`_CrtMemCheckpoint` 非常适合检测某段调用的 mem leak 问题.

 

3. 程序退出后的 mem leak 检测.

int _tmain(int argc, _TCHAR* argv [])
{
    // Set memory allocation in debug mode and 
    // dump the memory leak information after program exit.
    _CrtSetDbgFlag( 
                    _CRTDBG_ALLOC_MEM_DF                // Turn on debug allocation.
                    //| _CRTDBG_DELAY_FREE_MEM_DF        // Don't actually free memory.
                    //| _CRTDBG_CHECK_ALWAYS_DF            // Check heap every alloc/dealloc.
                    //| _CRTDBG_RESERVED_DF                // Reserved - do not use.
                    //| _CRTDBG_CHECK_CRT_DF            // Leak check/diff CRT blocks.
                    | _CRTDBG_LEAK_CHECK_DF                // Leak check at program exit.
                    );

    new int[1];

    return 0;
}

 

经验介绍:

1. 首先使用 `_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );` 在程序退出时检测整个程序是否有内存泄露.

2. 如果上面的检测有泄漏, 并且无法直观的肉眼找到, 则通过:

    _CrtMemState s1, s2, s3;
    _CrtMemCheckpoint( &s1 );
    foo();                        // 这是被检测的函数.
    _CrtMemCheckpoint( &s2 );
    if ( _CrtMemDifference( &s3, &s1, &s2) )
    {
        _CrtMemDumpStatistics( &s3 );
    }

这种方法可以快速定位到泄露点.

3. 最后, 根据自己情况, 配合使用 `_CrtDumpMemoryLeaks` 随时输出 mem 信息, 帮助分析即可.

 

msdn 勘误:

图中红圈部分, #define 说的是箭头所标记的地方. 但实际经过测试, 

 

而实际上, 如果想输出有意义的, 带 __FILE__ 以及 __LINE__ 的 dump 信息, 需要使用 `new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )`!

 

 

posted @ 2014-08-20 16:53  walfud  阅读(762)  评论(0编辑  收藏  举报