在 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
现在在您的 main
或 winmain
或程序的任何入口点的开头添加以下行:
//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++ 列表列表