检测可视C++ (Windows) 中的内存泄漏



我正在Visual Studio 2010下做一个大型C++项目,并认为里面有一些内存泄漏。我尝试了包含crtdbg.h的方法,但它没有多大帮助,因为我看不到泄漏发生的位置。定义 new 有两个陷阱:首先它需要在每个 cpp 文件中完成,这不是一个真正的选项,其次它中断了例如 Boost。使用 new(nothrow) 或任何使用 boost "has_new_operator.h" 的东西会破坏这一点。[编辑:它无法编译,因为重新定义的"new"没有像"nothrow"或"boostmagic"这样的重载](除非在所有提升标头(包括引用 boost 的标头)之后定义"new")

最后但并非最不重要的一点是:我有单身人士。它们是使用单例模板的子类和静态函数变量实现的。其中之一是配置容器,其中注册设置(字符串和整数对存储在映射中) 由于在释放单例实例之前调用了 mem 泄漏转储,因此所有这些字符串和单例本身都得到了大量的泄漏。

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

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

我使用视觉检漏仪取得了相当积极的结果。它小巧而整洁,可以在几秒钟内构建到您的项目中(假设您有一个正在运行的调试配置):

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:devprojectstmpmemleakmemleakmemory_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.0vcincludexmemory (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.0vcincludexmemory (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.0vcincludextree (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.0vcincludextree (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.0vcincludextree (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:devprojectstmpmemleakmemleakmain.cpp (33): memleak.exe!FooSingleton::addString + 0xA9 bytes
    d:devprojectstmpmemleakmemleakmain.cpp (51): memleak.exe!main + 0x37 bytes
    f:ddvctoolscrt_bldself_x86crtsrccrtexe.c (555): memleak.exe!__tmainCRTStartup + 0x19 bytes
    f:ddvctoolscrt_bldself_x86crtsrccrtexe.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();
}

泄漏永远不会被剖析和跟踪。

您可以从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"的新控制台程序之后:My 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> 

我的测试.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:UsersshrDocumentsVisual Studio 2010ProjectstestsDebugtests.exe', Symbols loaded.
'tests.exe': Loaded 'C:WindowsSysWOW64ntdll.dll', Cannot find or open the PDB file
'tests.exe': Loaded 'C:WindowsSysWOW64kernel32.dll', Cannot find or open the PDB file
'tests.exe': Loaded 'C:WindowsSysWOW64KernelBase.dll', Cannot find or open the PDB file
'tests.exe': Loaded 'C:WindowsSysWOW64msvcr100d.dll', Symbols loaded.
Detected memory leaks!
Dumping objects ->
c:usersshrdocumentsvisual studio 2010projectsteststeststests.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).

最新更新