在对象的d-tor期间检测内存泄漏

时间:2012-01-01 08:37:40

标签: c++ memory-leaks memory-management malloc

我的应用程序是基于DLL的实现,可能在卸载期间泄漏内存。我在卸载/重载循环期间注意到它(当托管进程没有被杀死时)。托管进程的虚拟内存正在增加。

我已经完成了代码检查,试图找到泄漏的代码,但没有找到任何代码。

我正在寻找其他技术来检测卸载过程中的内存泄漏(对象被破坏)。

编辑:我正在使用win32(XP)平台。

你有这方面的工具/程序经验吗? 感谢

4 个答案:

答案 0 :(得分:4)

我很久以前做的就是:

我写了自己的mymallocmyreallocmyfree(并重载newdelete,以便他们调用我的函数。)然后我写了调用了mallocrealloc的{​​{1}}和mymalloc个宏,传递了myrealloc__FILE____LINE__做的是:它调用标准库的mymalloc函数,分配稍大的块,并在该块中插入malloc__FILE__。它还将所有已分配的块保留在链表中,以便以后能够遍历它们。

程序退出后,我会遍历未释放的块列表,并打印出导致内存泄漏的文件和行。

如今,我认为会有现成的工具,你可以为你做这样的事情。

答案 1 :(得分:2)

您没有指定您要查找的平台。我是Windows开发人员所以我只能推荐Windows解决方案。如果您正在使用它,那么可以使用许多商业和免费工具。我个人合作的很少:Purify,BoundsChecker,UMDH,LeakDiag,DebugDiag。

其中,我通常更喜欢UMDH。它是免费的,是Debugging Tools for Windows (DTW)安装的一部分。我发现它实际上比大多数其他更可靠,资源更少,包括专业工具。它非常简单易用,文档可以在DTW安装附带的.chm文件中找到。最后,我个人发现UMDH与许多其他工具相比具有非常高的信噪比。

DebugDiag是另一个不错的选择。据我所知,它使用与UMDH几乎相同的API,但使用起来稍微麻烦,因为它是基于UI而不是命令提示符,所以要完成任务通常需要更多点击,但对于新的人,我会推荐它UMDH。

<强>更新

有趣的是,多数人偏好是将自定义钩子插入malloc / free,然后将更多自定义钩子的代码添加到运算符new / delete中。

我强烈建议你看看UMDH并了解它是如何工作的,即使你在这个特定情况下没有找到必要的东西。所有内存分配的核心是Windows函数HeapAlloc / HeapFree。微软预计需要泄漏检测方法,已经提供了我们可以在根级别使用的钩子。

使用UMDH而不是自定义分配器挂钩有以下优点:

  • 您获得每个分配的完整堆栈跟踪,而不仅仅是__FILE__和__LINE __
  • 提供的内容
  • 它已经有完整的统计报告和汇总,这是你必须在拦截malloc / free之上编写的。您获得每个跟踪的分配数,每个跟踪分配的字节数和分配的内存缓冲区列表,以便您可以实际分析泄漏的数据类型。
  • 检测在其他人的代码中发生的malloc / free泄漏,而不是特别是您控制下的DLL
  • 检测来自其他内存分配函数的泄漏,例如CoTaskMemAlloc或SysStringAlloc
  • 检测未正确释放的COM对象的泄漏
  • 当您调用第三方API时,检测代码中的逻辑错误,该第三方API会返回您忘记释放的缓冲区。
  • 您可以使用任何代码库立即使用UMDH,而无需反复添加自定义代码。
  • 您接受的方法仅适用于调试环境。无需在生产系统上进行任何代码更改即可有效地使用UMDH。

相当多,每当内存使用量呈上升趋势时,该工具会告诉您它的来源。大多数情况下,如果在开发机器上可以重现,我可以在10分钟内发现泄漏(调试生产代码需要更长的时间,因为符号文件必须匹配,有时需要手动匹配)。

因此,如果您通过DTW安装完全免费(其他btw具有其他令人敬畏的调试功能),为什么人们更愿意推出自己的泄漏检测代码?

答案 2 :(得分:1)

如果您可以在supported platform上编译代码,那么您一定要试试ValgrindMemcheck工具。阅读The Valgrind Quick Start Guide以了解具体方法。

这是一个简单的例子......

来源:

$ cat -n leaky.cpp 
     1  struct leaky
     2  {
     3      leaky()
     4          :bytes(new char[256])
     5      {
     6      }
     7  
     8      char* bytes;
     9  };
    10  
    11  int main()
    12  {
    13      leaky sieve;
    14      return sizeof sieve;
    15  }
    16  

构建

$ make leaky
g++ -Wall -Wextra -Wshadow -pedantic -Wno-long-long -Wfloat-equal -Wcast-qual -g -I/opt/local/include -Weffc++ -Wall -I /opt/local/include -L/opt/local/lib  leaky.cpp   -o leaky

检查:

$ valgrind --leak-check=full ./leaky
==85800== Memcheck, a memory error detector
==85800== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==85800== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==85800== Command: ./leaky
==85800== 
==85800== 
==85800== HEAP SUMMARY:
==85800==     in use at exit: 2,367 bytes in 33 blocks
==85800==   total heap usage: 33 allocs, 0 frees, 2,367 bytes allocated
==85800== 
==85800== 256 bytes in 1 blocks are definitely lost in loss record 6 of 9
==85800==    at 0xB823: malloc (vg_replace_malloc.c:266)
==85800==    by 0x5768D: operator new(unsigned long) (in /usr/lib/libstdc++.6.0.9.dylib)
==85800==    by 0x576DA: operator new[](unsigned long) (in /usr/lib/libstdc++.6.0.9.dylib)
==85800==    by 0x100000EE7: leaky::leaky() (leaky.cpp:4)
==85800==    by 0x100000EB3: main (leaky.cpp:13)
==85800== 
==85800== LEAK SUMMARY:
==85800==    definitely lost: 256 bytes in 1 blocks
==85800==    indirectly lost: 0 bytes in 0 blocks
==85800==      possibly lost: 0 bytes in 0 blocks
==85800==    still reachable: 2,111 bytes in 32 blocks
==85800==         suppressed: 0 bytes in 0 blocks
==85800== Reachable blocks (those to which a pointer was found) are not shown.
==85800== To see them, rerun with: --leak-check=full --show-reachable=yes
==85800== 
==85800== For counts of detected and suppressed errors, rerun with: -v
==85800== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 1 from 1)

答案 3 :(得分:1)

除了Mike的回答(覆盖Malloc),您还可以覆盖Visual Studio中的new和delete运算符。

免责声明:我在2004年在网上找到了这个代码并将其包含在C ++项目中。我不知道原始来源

下面是一个代码示例(我将其作为头文件包含在内,memleak.h)。这是非常古老的代码,因此它可能有错误编译!它确实说明了如何覆盖new和delete。它还将未使用的内存转储到文件中。如果您的代码中包含define _DEBUG,则此代码生效。

致以最诚挚的问候,

#include <iostream>
#include <list>
using namespace std;

//void DumpUnfreed();
//void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum);
//void RemoveTrack(DWORD addr);

typedef struct 
{
    DWORD   address;
    DWORD   size;
    char    file[64];
    DWORD   line;
} ALLOC_INFO;      

typedef list<ALLOC_INFO*> AllocList;   

AllocList *allocList; 

void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum)
{
    ALLOC_INFO *info;         
    if(!allocList) 
    {
        allocList = new(AllocList);
    }         
    info = new(ALLOC_INFO);
    info->address = addr;
    strncpy(info->file, fname, 63);
    info->line = lnum;
    info->size = asize;
    allocList->insert(allocList->begin(), info);
};      

void RemoveTrack(DWORD addr)
{
    AllocList::iterator i;        
    if(!allocList)
        return;
    for(i = allocList->begin(); i != allocList->end(); i++)
    {
        if((*i)->address == addr)
        {
            allocList->remove((*i));
            break;
        }
    }
};

void DumpUnfreed()
{
    AllocList::iterator i;
    DWORD totalSize = 0;
    char buf[1024];   
    sprintf(buf, "-----------------------------------------------------------\n");
    OutputDebugString(buf);
    OutputDebugString("DSP.DLL: Detecting unfreed memory...\n");
    if(!allocList)
    {
        OutputDebugString("No memory allocations were tracked!\n");
        return;       
    }
    for(i = allocList->begin(); i != allocList->end(); i++) 
    {
        sprintf(buf, "%-50s:\t\tLINE %d,\t\tADDRESS %d\t%d unfreed\n",
            (*i)->file, (*i)->line, (*i)->address, (*i)->size);
        OutputDebugString(buf);
        totalSize += (*i)->size;
    }
    sprintf(buf, "-----------------------------------------------------------\n");
    OutputDebugString(buf);
    sprintf(buf, "DSP.DLL Total Unfreed: %d bytes\n", totalSize);
    OutputDebugString(buf);
    sprintf(buf, "-----------------------------------------------------------\n");
    OutputDebugString(buf);
};

#ifdef _DEBUG
inline void * __cdecl operator new(unsigned int size,
                                         const char *file, int line)
{
    void *ptr = (void *)malloc(size);
    AddTrack((DWORD)ptr, size, file, line);
    return(ptr);
};
inline void __cdecl operator delete(void *p)
{
    RemoveTrack((DWORD)p);
    free(p);
};
inline void * __cdecl operator new[ ] (unsigned int size, const char *file, int line)
{  
    void *ptr = (void *)malloc(size);
    AddTrack((DWORD)ptr, size, file, line);
    return(ptr);                            
};
inline void __cdecl operator delete[ ] (void *p)
{
    RemoveTrack((DWORD)p);
    free(p);
};
#endif

#ifdef _DEBUG
#define DEBUG_NEW new(__FILE__, __LINE__)
#else
#define DEBUG_NEW new
#endif
#define new DEBUG_NEW