通过重载新运算符来查找 MFC C++应用中的内存泄漏

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

本文关键字:应用 C++ 泄漏 内存 MFC 查找 重载 运算符      更新时间:2023-10-16

我有一个MFC应用程序。我想跟踪每个动态内存分配(在堆上(,以便能够找出该应用程序中内存泄漏的来源。IDE是Visual Studio 2010。

我做了以下工作:

  • 引入了名为"MEMORY_LEAK_FINDER"的预处理器指令。
  • 添加了一个名为"CMemLeakHunter"的类,您可以找到确切的内容这些文件如下。
  • 这个想法是使每个新运算符过载(所有其中 3 个:new、new[] 和 CObject::new(,并使用它们来跟踪分配内存的位置(文件、行(。在结尾我想将内存泄漏的位置带到使用"CMemoryState"类输出,因此我最终可以将分配跟踪与CMemoryState的比较(差异(跟踪进行比较。

问题是,应用程序编译(在VS 2010调试模式下(,但发生以下链接器错误:

错误

4 错误 LNK2005:"void * __cdecl运算符 new[](无符号整数,字符常量 *,int("(??_U@YAPAXIPBDH@Z( 已在 CMemLeakHunter.obj E:\Software\Nafxcwd.lib(afxmem.obj( 中定义 错误 3 错误 LNK2005:"void * __cdecl运算符 new(无符号 int,char const *,int("(??2@YAPAXIPBDH@Z( 已在 CMemLeakHunter.obj E:\Software\Nafxcwd.lib(afxmem.obj( 中定义 错误 5 错误 LNK2005:"public: static void * __stdcall CObject::operator new(unsigned int,char const *,int(" (??2CObject@@SGPAXIPBDH@Z( 已在 CMemLeakHunter.obj E:\Software\Nafxcwd.lib(afxmem.obj( 中定义 错误 6 错误 LNK1169:找到一个或多个乘法定义的符号 E:\软件\模块 1.exe 1

我用谷歌搜索并发现,忽略Nafxcwd.lib库可能会解决问题。在我的应用程序中不是,我尝试了,但忽略了该库,另一个 17000 链接器错误(未解析的外部(。

其他依赖项包括:Nafxcwd.lib;Ws2_32.lib;Version.lib

忽略特定的默认库是:msvcprtd.lib;libcimtd.lib;libcmt.lib

不能如此轻松地拆分软件,因此我寻求帮助:如果我使用 MFC 并且需要使用上面提到的 .lib 文件,我如何跟踪我自己的应用程序完成的内存分配?解决方案是什么?请帮助我解决此问题,以便能够跟踪内存分配以找出泄漏的可能来源。如果他们能够做到这一点,我也愿意使用另一个 MFC 内置例程。但是,我自己没有发现任何有用的东西。

头文件 CMemLeakHunter.hpp 编写如下:

#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_

源文件 CMemLeakHunter.cpp 编写如下:

#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('', 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: %dn", 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: %dn", 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("%sn", 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

无需自己执行此操作。

在您的主要内容中.cpp包括:

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

在主函数调用的顶部:

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

对于要检测泄漏的每个文件,请将其放在顶部:

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

然后,任何检测到的泄漏都会在应用程序退出时输出到控制台。

看这里。