The analysis of memory leak detection of Crt in C++

  • 2020-04-01 21:40:47
  • OfStack

Although the concept is overused, I'd like to simply document it for future reference.


#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[])
{
    char* p = new char();
    char* pp = new char[10];
    char* ppp = (char*)malloc(10);
    _CrtDumpMemoryLeaks();
    return 0;
}

The main principle is to use the memory debugging function of Crt to replace the default operator new with a macro, so that it is replaced by the following version:

void *__CRTDECL operator new(
        size_t cb,
        int nBlockUse,
        const char * szFileName,
        int nLine
        )
        _THROW1(_STD bad_alloc)
{
    
    void *res = _nh_malloc_dbg( cb, 1, nBlockUse, szFileName, nLine );
    RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0));
    
    if (res == 0)
    {
        static const std::bad_alloc nomem;
        _RAISE(nomem);
    }
    return res;
}

In this way, the Crt records the file name and line number and size of the allocated memory, and finally _CrtDumpMemoryLeaks() is used when calling. It will print out if it hasn't been released yet.
The results are as follows:

Detected memory leaks!
Dumping objects ->
f:testmemleakcheckermemleakcheckermemleakchecker.cpp(23) : {108} normal block at 0x0003A1A8, 10 bytes long.
 Data: <          > CD CD CD CD CD CD CD CD CD CD 
f:testmemleakcheckermemleakcheckermemleakchecker.cpp(22) : {107} client block at 0x0003A160, subtype 0, 10 bytes long.
 Data: <          > CD CD CD CD CD CD CD CD CD CD 
f:testmemleakcheckermemleakcheckermemleakchecker.cpp(21) : {106} client block at 0x0003A120, subtype 0, 1 bytes long.
 Data: < > 00 
Object dump complete.

Here are some do's and don 'ts:
(1) function of #define _CRTDBG_MAP_ALLOC
If the macro is not defined, the C mode malloc leak will not be recorded.

(2) function of number {108} {107}
To indicate the number of allocations, you can pause the _CrtSetBreakAlloc program to a predetermined number of runs, for example


int _tmain(int argc, _TCHAR* argv[])
{
    _CrtSetBreakAlloc(108);
    char* p = new char();
    char* pp = new char[10];
    char* ppp = (char*)malloc(10);
    _CrtDumpMemoryLeaks();
    return 0;
}

(3) if the program has multiple exits or global variables are involved, the flag can be set by _CrtSetDbgFlag to automatically print the leak when the program exits, for example

int _tmain(int argc, _TCHAR* argv[])
{
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    char* p = new char();
    char* pp = new char[10];
    char* ppp = (char*)malloc(10);
    return 0;
}

(4) we know that macro substitution is the roughest way, so try to put the following new macro substitution in each Cpp instead of in a generic header file, which is exactly what MFC does

#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif

(5) the above operator new can only take care of the most common new. In fact, the operator new can be overloaded in any number of ways, just make sure that the first parameter is the size. For example, the following placement new fails to compile because the format of the macro is not up to par, so if your CPP USES a non-standard new, don't include new's detection macro.

#include <new>
#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[])
{
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    char* p = new char();
    char* pp = new char[10];
    char* ppp = (char*)malloc(10);
    char d;
    char* p1 = new(&d) char('a');
    return 0;
}

(6) because the tree in the map in the STL USES placement new,& provident; So if you use it this way it will fail to compile:

#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
#include <map>

You should put #include < Map> Put it in front of the macro definition.

(7) if you declare or define the operator new function after the macro #define new DEBUG_CLIENTBLOCK, the compiler will fail due to macro substitution.
STL's xdebug file declares the operator new function, so make sure that the new substitution macro is at the end of all the include headers, especially at the end of the STL header.


//MyClass.cpp
#include "myclass.h"
#include <map>
#include <algorithm>
#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif
MyClass::MyClass()
{
    char* p = new char('a');
}

(8) if you think the above new replacement macro is too cumbersome to be scattered among the various CPPS and want to put everything into a common header file, please refer to the following definition:

//MemLeakChecker.h 
#include <map>
#include <algorithm>
//other STL file
#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

(9) to simply determine whether an independent function has memory leaks, the following methods can be used:

class DbgMemLeak
{
    _CrtMemState m_checkpoint;
public:
    explicit DbgMemLeak() 
    {   
        _CrtMemCheckpoint(&m_checkpoint); 
    };
    ~DbgMemLeak()
    {
        _CrtMemState checkpoint;
        _CrtMemCheckpoint(&checkpoint);
        _CrtMemState diff;
        _CrtMemDifference(&diff, &m_checkpoint, &checkpoint);
        _CrtMemDumpStatistics(&diff);
        _CrtMemDumpAllObjectsSince(&diff);
    };
};

int _tmain(int argc, _TCHAR* argv[])
{
    DbgMemLeak check;
    {
        char* p = new char();
        char* pp = new char[10];
        char* ppp = (char*)malloc(10);
    }
    return 0;
}

(10) in fact, knowing the principle, it is not difficult to write a set of C++ memory leak detection, mainly by overloading operator new and operator delete, you can record each memory allocation in a Map, delete the record when delete, the last program exit without delete Map print out. Of course, we know that when Crt implements new, it is malloc that is actually up-regulated, and malloc may call HeapAlloc again, and HeapAlloc may call RtlAllocateHeap again, so theoretically we can intercept and record at any layer of these functions. But if you want to implement your own cross-platform memory leak detection, reload operator new.


Related articles: