在 Visual C++ (Windows) 中检测内存泄漏

Posted

技术标签:

【中文标题】在 Visual C++ (Windows) 中检测内存泄漏【英文标题】:Detecting memory leaks in Visual C++ (Windows) 【发布时间】:2014-11-23 09:18:43 【问题描述】:

我正在 Visual Studio 2010 下开发一个大型 C++ 项目,并认为内部存在一些内存泄漏。我尝试了包含 crtdbg.h 的方法,但它没有多大帮助,因为我看不到泄漏发生在哪里。定义 new 有两个陷阱:首先它需要在每个 cpp 文件中完成,这不是一个真正的选项,第二它打破了例如促进。使用 new(nothrow) 或任何使用 boosts "has_new_operator.h" 的东西打破了这一点。 [编辑:它无法编译,因为重新定义的“new”对于“nothrow”或“boost magic”之类的东西没有重载](除非在所有 boost 标头(包括引用 boost 的标头)之后定义“new”)

最后但同样重要的是:我有单身人士。它们是使用单例模板的子类和静态函数变量来实现的。其中之一是一个配置容器,其中一个注册设置(比存储在映射中的字符串和整数对)由于在解除分配单例实例之前调用了内存泄漏转储,因此所有这些字符串和单例本身。

有什么方法可以只显示真正的泄漏或在静态对象释放后转储?

哪些免费工具可以处理这种情况?

【问题讨论】:

有些工具不是免费的,但提供全功能试用。因此,您不必花钱检查是否有泄漏。 不太确定它是否正是您需要的,但 valgrind valgrind.org/docs/manual/mc-manual.html 提供了检测内存泄漏的工具。 【参考方案1】:

我使用了 Visual Leak Detector,结果非常好。它小巧整洁,可以在几秒钟内构建到您的项目中(假设您有一个正在运行的 Debug 配置):

https://vld.codeplex.com/

如果设置正确(可以使用安装程序完成),那么您只需

#include <vld.h> 

在每个模块的 一个 .cpp 文件中 - 就是这样,标题将为您进行链接。你不必把它放在任何地方。该工具在内部使用 CrtDbg,因此您必须运行调试版本才能使其工作。

它会在每次运行后为您提供调试器或文本输出(如果使用配置文件进行配置),即使未通过调试器运行也是如此。它不是最强大的工具,但这些通常要花一些钱;)

编辑:通过在包含标头之前定义VLD_FORCE_ENABLE,也可以在非调试配置中启用 VLD。但结果可能会有所缓和。

编辑:我已尝试全新安装 VLD。请注意,对于 VS2013 编译器,必须使用 v2.4rc2 版本(或更高版本的 v2.3)。 v2.3 版本仅适用于 VS2010 编译器。

安装后,我创建了一个新项目并设置了我的包含目录和库目录以包含相应的 VLD 文件夹。之后我用下面的代码测试了单例的 memleak 报告(注意这段代码没有意义,它只是证明了一点):

#include <iostream>
#include <string>
#include <sstream>
#include <map>

// Uncomment this, if you want VLD to work in non-debug configurations
//#define VLD_FORCE_ENABLE

#include <vld.h>

class FooSingleton 
    private:
        std::map<std::string, std::string*>
            _map;

        FooSingleton() 
        

    public:
        static FooSingleton* getInstance(void) 
            /* THIS WOULD CAUSE LEAKS TO BE DETECTED
               SINCE THE DESTRUCTOR WILL NEVER BE CALLEd
               AND THE MAP IS NOT CLEARED.
            */
            // FooSingleton* instance = new FooSingleton;
            // return instance;

            static FooSingleton instance;
            return &instance;
        

        void addString(const std::string& val) 
            _map.insert(std::make_pair(val, new std::string(val)));
        

        ~FooSingleton(void) 
            auto it = _map.begin();
            auto ite = _map.end();

            for(; it != ite; ++it) 
                delete it->second;
            
        
;

int main(int argc, char** argv) 
    FooSingleton* fs = FooSingleton::getInstance();
    for(int i = 0; i < 100; ++i) 
        std::stringstream ss;
        ss << i << "nth string.";
        fs->addString(ss.str());
    

    return 0;

使用此代码,VLD 不会报告任何泄漏,因为getInstance() 中的静态自动变量将在退出时被破坏,并且地图中的元素将被删除。尽管如此,即使是单例也必须这样做,否则将报告泄漏。但在这种情况下:

Visual Leak Detector Version 2.3 installed.
Aggregating duplicate leaks.
Outputting the report to the debugger and to D:\dev\projects\tmp\memleak\memleak\memory_leak_report.txt
No memory leaks detected. Visual Leak Detector is now exiting.

如果getInstance() 中的代码更改为注释版本,则永远不会清除单例并报告以下泄漏(以及其他泄漏):

---------- Block 11 at 0x008E5928: 52 bytes ----------
Leak Hash: 0x973608A9 Count: 100
  Call Stack:
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xmemory (36): memleak.exe!std::_Allocate<std::_Tree_nod<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::alloca + 0x15 bytes
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xmemory (187): memleak.exe!std::allocator<std::_Tree_nod<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::alloca + 0xB bytes
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xtree (560): memleak.exe!std::_Tree_val<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,s + 0xD bytes
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xtree (588): memleak.exe!std::_Tree_val<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,s + 0x8 bytes
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xtree (756): memleak.exe!std::_Tree<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,std:: + 0x17 bytes
    d:\dev\projects\tmp\memleak\memleak\main.cpp (33): memleak.exe!FooSingleton::addString + 0xA9 bytes
    d:\dev\projects\tmp\memleak\memleak\main.cpp (51): memleak.exe!main + 0x37 bytes
    f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (555): memleak.exe!__tmainCRTStartup + 0x19 bytes
    f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (371): memleak.exe!mainCRTStartup
    0x76BF919F (File and line number not available): KERNEL32.DLL!BaseThreadInitThunk + 0xE bytes
    0x7739A22B (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x84 bytes
    0x7739A201 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x5A bytes
  Data:
    C0 53 8E 00    30 67 8E 00    C0 53 8E 00    98 58 8E 00     .S..0g.. .S...X..
    30 6E 74 68    20 73 74 72    69 6E 67 2E    00 CD CD CD     0nth.str ing.....
    0C 00 00 00    0F 00 00 00    CD CD CD CD    48 56 8E 00     ........ ....HV..
    01 00 CD CD    

您可以清楚地看到这段代码的Count: 100,这是正确的。

我还在安装目录中编辑了我的vld.ini 文件以启用以下设置:

AggregateDuplicates = yes
ReportTo = both

这些确保 a) 所有重复的泄漏都被压缩到一个带有泄漏计数的报告中(如上所述,否则将有 100 个条目),另一个报告文件被转储到应用。

因此,对于单例,只要您使用正在使用的静态自动变量方法并在析构函数中进行清理,它就可以正常工作。

编辑:此外,可以在特定代码段禁用检测。如果上面的代码要这样修改:

void addString(const std::string& val) 
    VLDDisable();
    _map.insert(std::make_pair(val, new std::string(val)));
    VLDEnable();

泄漏将永远被分析和跟踪。

【讨论】:

如果它使用 crtDbg,它是否处理单例?我能做些什么让单例不再显示为泄漏吗? (该死的,我会喜欢 valgrind for windows 之类的东西......) @Flamefire 我认为单例会出现,因为如果在getInstance() 中动态创建它们,它们将永远不会被删除。老实说,我不能肯定地说,你必须自己检查,对不起。但是假设您期望这些泄漏,它们不应该给您带来麻烦并且可以很容易地被过滤掉 - 也因为它们只被报告一次,并且真正令人讨厌的泄漏出现了数百次;) @Flamefire 加法:如果你只是在你的getInstance() 中返回一个静态自动变量的地址(或者你访问你的单例)那么它应该没问题,因为它们将被清理出口。只是 new 永远不会有对应的 delete 可以由 VLD 检查。还有一个 API,您可以使用它在运行时启用/禁用 VLD(VLDDisable()VLDEnable())。如果你把它们放在你的单例分配中,这些可能会起作用。 真正的主要问题不是单例本身,而是其中的地图。我收到了 100 份关于泄露字符串和相关数据的报告。很难过滤掉那些。函数是这样的: T& getInstance()static T instance; return instance; 我刚刚发现我可以在 crtDbg 中使用内存存储点(例如在指定的时间范围内获取内存泄漏)是否也可以使用 VLD 等其他工具?我认为这将解决单例问题,除非在其间插入新字符串。 @Flamefire 嗯,我不确定是否有其他免费工具可以做到这一点(不是说没有)。但是 VLD 报告泄漏的方式是它会打印调用堆栈 once 并计算该段代码泄漏的频率。因此,即使有 100 多个泄漏,您的日志中也只会有一个条目(再次,应该很容易被过滤掉)。您可以使用上述 API 调用在一段时间内启用/禁用 VLD(有点)。我认为最好的办法就是尝试一下。与此同时,可能还有其他答案:)【参考方案2】:

您可以从crtdebug 获取内存泄漏源。它不会帮助您进行 boost 分配,除非您以相同的方式编译 boost(或任何库),但对于其余部分,它将向您显示分配文件和行。

这就是你正确使用crtdebug.h的方式:

stdafx.h(或任何 PCH 文件)的顶部添加以下行:

#ifdef DEBUG
    //must define both _CRTDBG_MAP_ALLOC and _CRTDBG_MAP_ALLOC_NEW
    #define _CRTDBG_MAP_ALLOC
    #define _CRTDBG_MAP_ALLOC_NEW

    #include <stdlib.h>
    #include <crtdbg.h>
    //if you won't use this macro you'll get all new as called from crtdbg.h      
    #define DEBUG_NEW   new( _CLIENT_BLOCK, __FILE__, __LINE__)
    #define new DEBUG_NEW
#endif

现在在您的 mainwinmain 或程序的任何入口点的开头添加以下行:

//register memory leak check at end of execution: 
//(if you use this you won't need to use _CrtDumpMemoryLeaks at the end of your main)
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
//set report mode:
_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );

现在我做了一个小测试:

在来自 VS10 的一个名为“test”的新控制台程序之后: 我的 stdafx.h:

#pragma once

#ifdef _DEBUG
  #define _CRTDBG_MAP_ALLOC
  #define _CRTDBG_MAP_ALLOC_NEW
  #include <stdlib.h>
  #include <crtdbg.h>

  #define DEBUG_NEW   new( _CLIENT_BLOCK, __FILE__, __LINE__)
  #define new DEBUG_NEW
#endif

#include "targetver.h"

#include <stdio.h>
#include <tchar.h> 

我的 test.cpp 是:

#include "stdafx.h"
void CheckMemoryLeak()

    char *ptr=new char[100];
    int n=900;
    sprintf(ptr,"%d",n);


int _tmain(int argc, _TCHAR* argv[])

    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );

    CheckMemoryLeak();

    return 0;

输出是:

'tests.exe': Loaded 'C:\Users\shr\Documents\Visual Studio 2010\Projects\tests\Debug\tests.exe', Symbols loaded.
'tests.exe': Loaded 'C:\Windows\SysWOW64\ntdll.dll', Cannot find or open the PDB file
'tests.exe': Loaded 'C:\Windows\SysWOW64\kernel32.dll', Cannot find or open the PDB file
'tests.exe': Loaded 'C:\Windows\SysWOW64\KernelBase.dll', Cannot find or open the PDB file
'tests.exe': Loaded 'C:\Windows\SysWOW64\msvcr100d.dll', Symbols loaded.
Detected memory leaks!
Dumping objects ->
c:\users\shr\documents\visual studio 2010\projects\tests\tests\tests.cpp(9) : 97 client block at 0x01003288, subtype 0, 100 bytes long.
 Data: <900             > 39 30 30 00 CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.
The program '[1600] tests.exe: Native' has exited with code 0 (0x0).

【讨论】:

如问题中所述:这无济于事。我需要在项目的每个 cpp 文件中执行此操作,或者在每个 cpp 文件中包含一个标头(stdafx.h 未始终使用)而且在许多情况下它会失败(boost,nothrow,...)由于宏. 1.如果您将使用它编译 boost 或任何其他库,那么它也会检测库中的内存泄漏。 2. Visual Studio 默认使用预编译头文件,你也可以修复crtdbg.h 但无论如何你需要将它包含在任何源文件的顶部,这是相同的工作。 对不起,我不够清楚:使用 boost 会破坏编译!如前所述:我不能仅仅为了检查内存泄漏而改变整个大项目。

以上是关于在 Visual C++ (Windows) 中检测内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

无法在 Visual Studio 中将 Visual C++ 运行时包引用添加到 Windows Phone 项目

指针对象在 Visual C++ 6.0 中创建 windows 错误

在 Windows 上安装 OpenCV 并使用 Visual Studio C++ 构建程序

Windows Visual Studio C++ 列表列表

在 Visual C++ 中的 Windows 窗体应用程序上使用 OpenGL

Windows 8 上的 Visual C++ 6.0