Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VisualC++

Memory Allocation Tracking for C++ Code

5.00/5 (5 votes)
1 Nov 2020CPOL3 min read 11.1K  
A Handy Memory Allocation Tracking Macro and Header for Visual Studio C++ Code
This tip presents a handy memory allocation tracking macro and series of operators for use with the Visual Studio C++ compiler.

Introduction

The VS compiler has two functions in its run-time library for tracking memory allocations, _malloc_dbg and _free_dbg. They are passed a file name and line number to help determine where unreleased memory was allocated. These functions are typically used only when _DEBUG is enabled where a family of macros define the standard malloc, calloc, and free calls to use these debug versions. One can also use a macro to define special operators to help track memory allocations and object creation using new and delete in C++ code. Microsoft's MFC does this with the following macro:

C++
#ifdef _DEBUG
#define new   DEBUG_NEW
#endif

These facilities built into the Visual Studio compiler work quite well. However, when you have a development environment where different libraries and frameworks are utilized problems can arise. In mine we have apps written with MFC, some with Qt, and some in plain Win32. We also have libraries written for the respective frameworks. When I attempted to reimplement the DEBUG_NEW macro for non-MFC use multiple definition link errors arose in MFC libraries and apps for these:

error LNK2005: "void * __cdecl operator new(unsigned __int64,char const *,int)"
error LNK2005: "void * __cdecl operator new[](unsigned __int64,char const *,int)"
error LNK2005: "void __cdecl operator delete(void *,char const *,int)"

After considerable head-scratching, I came up with a work-around that allows non MFC-based libraries to co-exist with MFC-based libraries and to be used in MFC-based apps. It involves adding an unused flag to a set of operators. This flag gives the three functions noted above a different signature and this keeps the linker happy. This file originated from the MFC implementation.

From the code implementation point of view there are virtually no changes. One still uses new and delete as before, along with malloc, calloc, and free, but the macro definition for new changes from the one used for MFC. Here is the one I use:

C++
#ifdef _DEBUG
#define new     DebugNew
#endif

This macro is the front-end for a series of inline functions in a single header file that provides a side-by-side implementation of MFC's functions which allow them to co-exist. Here is the header file:

C++
//
// MemoryDebug.h : memory allocation debugging header file - redefines new and delete operators
//
// adapted from MFC by Rick York, a fan of http://www.codeproject.com
//
/***
To use this add the following to source modules :

#ifdef _DEBUG
#define new     DebugNew
#endif
 ***/

#pragma once
#define MEMORYDEBUG_H
// #include "MemoryDebug.h"

#ifdef _DEBUG

#define DebugNew    new( __FILE__, __LINE__, 0 )

///////////////////////////////////////////////////////////////

inline void * __cdecl
operator new(
                size_t size
                , std::align_val_t type
                , PCTSTR fileName
                , int line
                , int        // flag
                )
{
    void * pResult = nullptr;

//    for(;;)        // MFC's implementation keeps trying until it works
    {
        pResult = _malloc_dbg( size, (int) type, fileName, line );
//        if( pResult ) break;
    }
    return pResult;
}

///////////////////////////////////////////////////////////////

inline void __cdecl
operator delete(
                void* p
                , std::align_val_t type
                , PCTSTR    // fileName
                , int        // line
                , int        // flag
                )
{
    _free_dbg( p, (int) type );
}

///////////////////////////////////////////////////////////////

inline void * __cdecl
operator new[](
                size_t size
                , std::align_val_t type
                , PCTSTR fileName
                , int line
                , int flag
                )
{
    return ::operator new( size, type, fileName, line, flag );
}

///////////////////////////////////////////////////////////////

inline void __cdecl
operator delete[](
                void * p
                , std::align_val_t type
                , PCTSTR fileName
                , int line
                , int flag
                )
{
    ::operator delete( p, type, fileName, line, flag );
}

///////////////////////////////////////////////////////////////

inline void * __cdecl
operator new(
                size_t size
                , PCTSTR fileName
                , int line
                , int flag
                )
{
    return ::operator new( size, (std::align_val_t) _NORMAL_BLOCK,
                            fileName, line, flag );
}

///////////////////////////////////////////////////////////////

inline void * __cdecl
operator new[](
                size_t size
                , PCTSTR fileName
                , int line
                , int flag
                )
{
    return ::operator new[]( size, (std::align_val_t) _NORMAL_BLOCK,
                            fileName, line, flag );
}

///////////////////////////////////////////////////////////////

inline void __cdecl
operator delete(
                void* pData
                , PCTSTR    // fileName
                , int        // line
                , int        // flag
                )
{
    ::operator delete( pData );
}

///////////////////////////////////////////////////////////////

inline void __cdecl
operator delete[](
                void* pData
                , PCTSTR    // fileName
                , int        // line
                , int        // flag
                )
{
    ::operator delete( pData );
}

#endif // _DEBUG

As you can see, the C++ operators just call the run-time library functions to allocate and release memory. These operators provide memory allocation tracking for non-MFC C++ code. Actually, MFC C++ code can use them too by appropriately defining the new operator. It is important to note that virtually any macro can be used in place of DebugNew but it has to match the one in the header file.

Two last reminders: to enable memory tracking in apps built with Visual Studio's C++ compiler, include the following for allocation mapping:

C++
#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <CRTdbg.h>
#endif

and add the following snippet into your program's initialization:

C++
#ifdef _DEBUG

    int tmp = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
    tmp |= _CRTDBG_LEAK_CHECK_DF;
//    tmp &= ~(_CRTDBG_CHECK_CRT_DF );
    _CrtSetDbgFlag( tmp );

#endif // _DEBUG

The first flag (or'ed in) tells the library to check for leaks at program termination. The second flag (and'ed out) tell the library to not report on CRTL allocations. It can be optionally enabled or not as desired.

When you add the preceding code to your program, Visual Studio's debugger will inform you when you have neglected to release memory and objects.

This article provides more information about the CRTL memory allocation tracking facilities.

One interesting point to note is this header file and the macros provide the blueprint for implement your own memory management and tracking system.  To do this one would need to redefine the macros in Visual Studio's CRTdbg.h header file and this one to have them utilize your custom _malloc_dbg and _free_dbg functions.

History

  • 1st November, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)