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:
#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:
#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:
#pragma once
#define 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 )
{
void * pResult = nullptr;
{
pResult = _malloc_dbg( size, (int) type, fileName, line );
}
return pResult;
}
inline void __cdecl
operator delete(
void* p
, std::align_val_t type
, PCTSTR , int , int )
{
_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 , int , int )
{
::operator delete( pData );
}
inline void __cdecl
operator delete[](
void* pData
, PCTSTR , int , int )
{
::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:
#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <CRTdbg.h>
#endif
and add the following snippet into your program's initialization:
#ifdef _DEBUG
int tmp = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
tmp |= _CRTDBG_LEAK_CHECK_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