Finding memory leaks in MFC C++ app by overloading new operators

728 views Asked by At

I have an MFC application. I would like to trace every dynamic memory allocations (on heap) to be able to find out the source of the memory leaks in that app. The IDE is Visual Studio 2010.

I did the following:

  • Introduced a preprocessor directive called 'MEMORY_LEAK_FINDER'.
  • Added a class called 'CMemLeakHunter', you find the exact content of these files below.
  • The idea was to overload every new operators (all 3 of them: new, new[] and CObject::new) and use them to trace the location where the memory was allocated (file, line). At the end of the execution I wanted to bring the memory leaks' location to the output using 'CMemoryState' class, so I could finally compare the allocations' trace with the CMemoryState's compare (difference) trace.

The problem is, that the application compiles (in VS 2010 debug mode), but the following linker errors are occurred:

Error 4 error LNK2005: "void * __cdecl operator new[](unsigned int,char const *,int)" (??_U@YAPAXIPBDH@Z) already defined in CMemLeakHunter.obj E:\Software\Nafxcwd.lib(afxmem.obj) Error 3 error LNK2005: "void * __cdecl operator new(unsigned int,char const *,int)" (??2@YAPAXIPBDH@Z) already defined in CMemLeakHunter.obj E:\Software\Nafxcwd.lib(afxmem.obj) Error 5 error LNK2005: "public: static void * __stdcall CObject::operator new(unsigned int,char const *,int)" (??2CObject@@SGPAXIPBDH@Z) already defined in CMemLeakHunter.obj E:\Software\Nafxcwd.lib(afxmem.obj) Error 6 error LNK1169: one or more multiply defined symbols found E:\Software\Module1.exe 1

I googled and found out, that ignoring the library Nafxcwd.lib may solve the problem. In my application not, I tried out, but ignoring that library, another 17000 linker error (unresolved externals).

Additional dependencies are: Nafxcwd.lib;Ws2_32.lib;Version.lib

Ignore specific default libraries are: msvcprtd.lib;libcimtd.lib;libcmt.lib

I can't split the software so easily, therefore I ask for help: how could I trace the memory allocations done by my own app if I am using MFC and I need to use the .lib files mentioned above? What could be the solution? Please help me to resolve this matter to be able to trace the memory allocations to find out the possible sources of the leaks. I'm also open-minded to use another MFC built-in routines if they are able to do this. However, I did not find any useful by myself.

The header file CMemLeakHunter.hpp is written as follows:

#ifndef _MEM_LEAK_HUNTER_
#define _MEM_LEAK_HUNTER_

#ifdef MEMORY_LEAK_FINDER
#pragma message("Macro MEMORY_LEAK_FINDER is active, overloading new...")

#include "stdafx.h"
#include <map>
using std::map;

#undef new
void* operator new(size_t size, LPCSTR file, int line);
void* operator new[](size_t size, LPCSTR file, int line);
#define new new(__FILE__, __LINE__)

namespace
{
    static const size_t LOG_BUFFER_SIZE = 2;
    static const size_t DEFAULT_BUFFER_LINE_SIZE = 512;
}

class CMemLeakHunter
{
public:
    static CMemLeakHunter& singleton();

    void startMemoryTrace(const char* leakPath, const char* allocPath);
    void endMemoryTrace();

    void addMemory(void* area, size_t size, LPCSTR file, int line);
    void deleteMemory(void* area, LPCSTR file, int line);

private:
    void flushAllocLog();
    void filterTrace();

    map<DWORD, size_t> mMemChunks;
    CString sLogBuffer[LOG_BUFFER_SIZE];
    size_t nLogBufferLines;

    CMemoryState oldMemState;
    CMemoryState newMemState;
    CMemoryState diffMemState;

    CString sMemLeakTracePath;
    CString sMemAllocTracePath;

    static CMutex s_oObjMutex;
};

#endif // MEMORY_LEAK_FINDER

#endif // _MEM_LEAK_HUNTER_

The source file CMemLeakHunter.cpp is written as follows:

#include "CMemLeakHunter.hpp"

#ifdef MEMORY_LEAK_FINDER
#pragma message("Memory-Leak finder is activated, building functions for the class...")

#undef new
void* operator new(size_t size, LPCSTR file, int line)
{
    void* pArea = ::operator new(size);
    CMemLeakHunter::singleton().addMemory(pArea, size, file, line);
    return pArea;
}

void* operator new[](size_t size, LPCSTR file, int line)
{
    void* pArea = ::operator new[](size);
    CMemLeakHunter::singleton().addMemory(pArea, size, file, line);
    return pArea;
}

void* PASCAL CObject::operator new(size_t size, LPCSTR file, int line)
{
    void* pArea = CObject::operator new(size);
    CMemLeakHunter::singleton().addMemory(pArea, size, file, line);
    return pArea;
}

CMutex CMemLeakHunter::s_oObjMutex;

CMemLeakHunter& CMemLeakHunter::singleton()
{
    static CMemLeakHunter theSingleObject;
    return theSingleObject;
}

void CMemLeakHunter::startMemoryTrace(const char* leakPath, const char* allocPath)
{
    sMemLeakTracePath = leakPath;
    sMemAllocTracePath = allocPath;

    oldMemState.Checkpoint();

    for(size_t i=0; i<LOG_BUFFER_SIZE; i++)
    {
        sLogBuffer[i] = CString('\0', DEFAULT_BUFFER_LINE_SIZE);
    }
}

void CMemLeakHunter::endMemoryTrace()
{
    newMemState.Checkpoint();
    if(FALSE != diffMemState.Difference(oldMemState, newMemState))
    {
        CFile oDumpFile;
        if(TRUE == oDumpFile.Open(sMemLeakTracePath, CFile::modeCreate | CFile::modeReadWrite | CFile::shareDenyWrite))
        {
            CFile* oldContext = afxDump.m_pFile;
            afxDump.m_pFile = &oDumpFile;

            diffMemState.DumpStatistics();
            oDumpFile.Write("\n", 1);
            diffMemState.DumpAllObjectsSince();

            oDumpFile.Close();

            afxDump.m_pFile = oldContext;
        }
        else
        {
            // TODO: log that file cannot be created!!!
        }
    }

    flushAllocLog();
    filterTrace();
}

void CMemLeakHunter::addMemory(void* area, size_t size, LPCSTR file, int line)
{
    CSingleLock oMemHunterTraceLock(&s_oObjMutex, TRUE);

    DWORD nAreaAddr = reinterpret_cast<DWORD>(area);
    mMemChunks[nAreaAddr] = size;

    if(nLogBufferLines >= LOG_BUFFER_SIZE)
    {
        flushAllocLog();
    }

    sLogBuffer[nLogBufferLines++].Format("### Memory allocation: Address 0x%08X, Size: %u, File: '%s', Line: %d\n", nAreaAddr, size, file, line);
}

void CMemLeakHunter::deleteMemory(void* area, LPCSTR file, int line)
{
    CSingleLock oMemHunterTraceLock(&s_oObjMutex, TRUE);

    DWORD nAreaAddr = reinterpret_cast<DWORD>(area);
    mMemChunks.erase(nAreaAddr);

    if(nLogBufferLines >= LOG_BUFFER_SIZE)
    {
        flushAllocLog();
    }

    sLogBuffer[nLogBufferLines++].Format("!!! Memory release: Address 0x%08X, File: '%s', Line: %d\n", nAreaAddr, file, line);
}

void CMemLeakHunter::flushAllocLog()
{
    CStdioFile oAllocFile;
    if(FALSE == PathFileExists(sMemAllocTracePath))
    {
        oAllocFile.Open(sMemAllocTracePath, CFile::modeCreate);
        oAllocFile.Close();
    }

    if(TRUE == oAllocFile.Open(sMemAllocTracePath, CFile::modeReadWrite | CFile::shareDenyWrite))
    {
        oAllocFile.SeekToEnd();
        for(size_t i=0; i<min(nLogBufferLines, LOG_BUFFER_SIZE); i++)
        {
            oAllocFile.WriteString(sLogBuffer[i]);
        }

        oAllocFile.Close();
    }
    else
    {
        // TODO: log that file cannot be accessed!!!
    }

    nLogBufferLines = 0;
}

void CMemLeakHunter::filterTrace()
{
    CStdioFile oAllocFile;
    if(TRUE == oAllocFile.Open(sMemAllocTracePath, CFile::modeRead | CFile::shareDenyWrite))
    {
        CString sFilterTraceFile;
        sFilterTraceFile.Format("filter_%s", sMemAllocTracePath);

        CStdioFile oFilterAllocFile;
        if(TRUE == oFilterAllocFile.Open(sFilterTraceFile, CFile::modeCreate | CFile::modeReadWrite | CFile::shareDenyWrite))
        {
            for (std::map<DWORD, size_t>::iterator it=mMemChunks.begin(); it!=mMemChunks.end(); it++)
            {
                CString addrHex;
                addrHex.Format("0x%08X", it->first);

                CString sLine;
                while(FALSE != oAllocFile.ReadString(sLine))
                {
                    if(sLine.Find(addrHex) > -1)
                    {
                        CString sLineWithNewline;
                        sLineWithNewline.Format("%s\n", sLine);
                        oFilterAllocFile.WriteString(sLineWithNewline);
                    }
                }
            }

            oFilterAllocFile.Close();
        }
        else
        {
            // TODO: log that file cannot be created!!!
        }

        oAllocFile.Close();
    }
    else
    {
        // TODO: log that file cannot be accessed!!!
    }
}

#endif // MEMORY_LEAK_FINDER
1

There are 1 answers

1
sjdowling On BEST ANSWER

No need to do this yourself.

In your main.cpp include:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

and at the top of your main function call:

#ifdef _DEBUG
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

For every file where you want to detect leaks place this at the top:

#ifdef _DEBUG
   #ifndef DBG_NEW
      #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
      #define new DBG_NEW
   #endif
#endif  // _DEBUG

Then any detected leaks are output to the console on application exit.

See here.