内存泄漏,如何使用MS Visual Studio 2012检测导致它的原因?

时间:2013-12-14 13:50:20

标签: c++ visual-studio memory-leaks

我一直在调整this code(c ++版本)以满足我的需求,这需要运行很长的视频序列。几百帧后,程序因内存问题而崩溃。如果我检查进程使用的内存,它使用超过1.6 GB的RAM。检查内存使用情况(使用Windows任务管理器以非常愚蠢的方式..)我注意到每个帧都使用了相同数量的额外内存。

我被告知使用MS Visual Studio(我目前正在使用的IDE)我应该能够在某个时刻看到哪些变量正在使用更多内存,以便找到哪一个导致了内存泄漏并修复它,但我在IDE中找不到任何相关内容。

如果可能,我不想使用像Valgrind这样的工具并使用Visual Studio解决问题。如果不是......那么,当我到达那里时,我会想到这一点:)

谢谢!

3 个答案:

答案 0 :(得分:2)

我发现微软的UMDH(“用户模式转储堆”)工具一次又一次地救了我。您可以找到详细信息on this page。为了简化使用它,我在这里包含了一个玩具程序,其中包含将UMDH集成到您自己的程序中所需的一切。

玩具程序在不释放内存的情况下分配1000个字符,并包含您需要的UMDH脚手架。这是源代码:

#include <windows.h>
#include <stdio.h>
#include <assert.h>

bool SpawnProcessWin32 (char *command, DWORD &returnCode)
{
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    bool ret = false;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    // Start the child process.
    if( !CreateProcessA (NULL,      // No module name (use command line).
                        command,    // Command line.
                        NULL,       // Process handle not inheritable.
                        NULL,       // Thread handle not inheritable.
                        FALSE,      // Set handle inheritance to FALSE.
                        0,          // No creation flags.
                        NULL,       // Use parent's environment block.
                        NULL,       // Use parent's starting directory.
                        &si,        // Pointer to STARTUPINFO structure.
                        &pi )       // Pointer to PROCESS_INFORMATION structure.
        )
    {
        DWORD error = GetLastError ();
        printf ("error = %d\n", error);
        goto exit;
    }

    // Wait until child process exits.
    WaitForSingleObject (pi.hProcess, INFINITE);

    // Check exit code
    DWORD dwExitCode = 0;

    GetExitCodeProcess (pi.hProcess, &dwExitCode);

    if(dwExitCode == STILL_ACTIVE)    
    {
        // Process did not terminate -> force it
        TerminateProcess (pi.hProcess, 0); // Zero is the exit code in this example
        returnCode = 0;
    }
    else
    {
        returnCode = dwExitCode;
    }

    // Close process and thread handles.
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );

    ret = true;

exit:
    return ret;
}

void foo ()
{
    char *buffer = new char [1000];
}

void main (int argc, char *argv[])
{
#if defined (USE_UMDH)
    char strCommand [1024];
    bool spawnRet;
    DWORD retCode;

    // For debugging purposes, take initial snapshot of memory
    sprintf (strCommand, "C:\\junk\\umdh.exe -p:%ld -f:c:\\junk\\findleak.dump.1", GetCurrentProcessId());
    spawnRet = SpawnProcessWin32 (strCommand, retCode);
    assert (spawnRet);
#endif

    foo ();        // This function will leak memory on purpose

#if defined (USE_UMDH)

    // For debugging purposes, take terminal snapshot of memory
    sprintf (strCommand, "C:\\junk\\umdh.exe -p:%ld -f:c:\\junk\\findleak.dump.2", GetCurrentProcessId());
    spawnRet = SpawnProcessWin32 (strCommand, retCode);
    assert (spawnRet);

    // Now take a diff of the two dumps
    sprintf (strCommand, "C:\\junk\\umdh.exe -d c:\\junk\\findleak.dump.1 c:\\junk\\findleak.dump.2 -f:c:\\junk\\findleak.dump.diff", GetCurrentProcessId());
    spawnRet = SpawnProcessWin32 (strCommand, retCode);
    assert (spawnRet);
#endif
}

以下是需要注意的要点:

  1. UMDH实用程序包含在Windows独立调试工具中。下载并安装调试工具(例如,see this page)。
  2. umdh.exe实用程序在程序中启动三次:一次在启动应用程序之前创建内存快照,一次在应用程序完成时创建内存快照,最后一次创建快照差异。
  3. 我每次都编写了一个名为SpawnProcessWin32的方便函数来启动umdh.exe程序。
  4. 我在代码中插入了“#if defined(USE_UMDH)”来启用/禁用UMDH代码。在我写的项目中,我喜欢留下UMDH脚手架代码以备将来使用。如果我怀疑内存泄漏,我只需定义USE_UMDH,并将所有内容设置为debug。
  5. 修改传递给SpawnProcessWin32()的命令以满足您的需要。在我的示例中,我将umdh.exe放在我的c:\ junk文件夹中,并让umdh.exe将其输出放在同一个文件夹中。
  6. 当您运行应用程序时,只需检查上次调用umdh.exe生成的diff文件。以下是我对上述玩具计划的看法:

    // Debug library initialized ...
    DBGHELP: DemoUmdh - private symbols & lines 
            c:\junk\TestProjects\DemoUmdh\Debug\DemoUmdh.pdb
    DBGHELP: ntdll - export symbols
    DBGHELP: KERNEL32 - export symbols
    DBGHELP: KERNELBASE - export symbols
    DBGHELP: MSVCR90D - export symbols
    //                                                                          
    // Each log entry has the following syntax:                                 
    //                                                                          
    // + BYTES_DELTA (NEW_BYTES - OLD_BYTES) NEW_COUNT allocs BackTrace TRACEID 
    // + COUNT_DELTA (NEW_COUNT - OLD_COUNT) BackTrace TRACEID allocations      
    //     ... stack trace ...                                                  
    //                                                                          
    // where:                                                                   
    //                                                                          
    //     BYTES_DELTA - increase in bytes between before and after log         
    //     NEW_BYTES - bytes in after log                                       
    //     OLD_BYTES - bytes in before log                                      
    //     COUNT_DELTA - increase in allocations between before and after log   
    //     NEW_COUNT - number of allocations in after log                       
    //     OLD_COUNT - number of allocations in before log                      
    //     TRACEID - decimal index of the stack trace in the trace database     
    //         (can be used to search for allocation instances in the original  
    //         UMDH logs).                                                      
    //                                                                          
    
    
    +    1036 (   1036 -      0)      1 allocs  BackTrace2
    +       1 (      1 -      0)    BackTrace2  allocations
    
        ntdll!RtlLogStackBackTrace+7
        ntdll!RtlCreateUserThread+15BE5
        ntdll!RtlInitializeCriticalSectionEx+129
        MSVCR90D!malloc_base+EE
        MSVCR90D!malloc_dbg+306
        MSVCR90D!malloc_dbg+BF
        MSVCR90D!malloc_dbg+6C
        MSVCR90D!malloc+1B
        MSVCR90D!operator new+11
        DemoUmdh!foo+28 (c:\junk\testprojects\demoumdh\main.cpp, 64)
        DemoUmdh!main+A8 (c:\junk\testprojects\demoumdh\main.cpp, 85)
        DemoUmdh!__tmainCRTStartup+1A8 (f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c, 586)
        DemoUmdh!mainCRTStartup+F (f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c, 403)
        KERNEL32!BaseThreadInitThunk+E
        ntdll!RtlInitializeExceptionChain+85
        ntdll!RtlInitializeExceptionChain+58
    
    
    Total increase ==   1036 requested +     28 overhead =   1064
    

    差异报告在应用程序完成后总共增加了大约1036个字节,表明存在泄漏。调用堆栈跟踪显示泄漏的确切位置:

    DemoUmdh!foo+28 (c:\junk\testprojects\demoumdh\main.cpp, 64)
    

    这对应于真正的罪魁祸首:

    char *buffer = new char [1000];

    希望这有帮助。

答案 1 :(得分:1)

虽然可以使用CRT进行调试以追踪内存泄漏,但我建议Visual Leak Detector:很好,直接并且集成到Visual Studio 2008,2010和2012中。

优点:

  • 集成
  • 简单用法(一个包括使其正常运行)

缺点:

  • 大块泄漏可能会非常慢,因为它会显示整个泄漏块
PS:对这篇文章未经拼写检查的时间道歉:我责怪我的finingerses固件的制造商没有及时发布更新! ;-)

如果它只是像这样工作那么我会有完美的拼写! If only it worked like this then I would have perfect spelling!

答案 2 :(得分:0)

对于非常基本和手工制作的内存泄漏搜索,可以使用MS Runtime中的泄漏检测版本。

Microsoft artikel

使用转储,稍后设置断点。这适用于可重现的算法/工作流程。

  • 第一次运行
  • 在给定点转储不是免费的内存
  • 将断点设置为引用mem指针

也许这可能有所帮助。