关于vs开发windows程序过程中内存检查二三事

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ChaffererZ/article/details/50669686

做为一个C/C++程序员,面对资源管理是必不可少的。今天,我对我这些年的经验的一些总结。

每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆。
C 语言程序使用一对标准库函数 malloc 和 free 在自由存储区中分配存储空间,而 C++ 语言则使用 new 和 delete 表达式实现相同的功能。

记得刚开始工作时,面试时就会被问内存管理时有哪些需要注意的?那时就会说需要“成对”使用 malloc 和 free 或者 new 和 delete ( new[] 和 delete[] )、重载 new 运算符以及使用 Smart-Ptr ,但是当再问还有没有时就会…

当然,在开发过程,除非有人严格的 review 你的代码,否则,任何人都有可能会有失误而犯申请的资源未被释放的错误。在经历几次 review 后发现,人工去跟进资源问题,是一件多么痛苦的事情。


引入第一个概念:静态代码检查工具

所谓静态代码检查就是使用一个工具检查我们写的代码是否安全和健壮,是否有隐藏的问题。

比如无意间写了这样的代码:
int n = 10;
char* buffer = new char[n];
buffer[n] = 0;

阿里云2018双11云服务只需99.5元
1核2G内存,¥99.5/年
2核4G内存,¥545.00/1年
2核4G内存,¥927.00/2年
2核4G内存,¥1227.00/3年
2核8G内存,¥2070.00/3年
直达入口:http://t.cn/EZ14u8r

这完全是符合语法规范的,但是静态代码检查工具会提示此处会溢出。也就是说,它是一个更加严格的编译器。

使用比较广泛的静态代码检查工具有 CppCheckPC-Lint 等。

PC-Lint 是资格最老,最强力的代码检查工具,但是是收费软件,并且配置起来有一点点麻烦。CppCheck 是免费的开源软件。使用起来也很方便。详细使用传送门

最开始使用 CppCheck 时,各种跟文档、拼命令行,那叫一个痛苦。纠结了一段时间后,意外的发现了 Riverblade 提供的 Visual Lint,大大增加了我对把控代码质量的信心。

Visual Lint 基本上是 C/C++ 开发者编写高质量程序的必备工具,这个插件可以很好的实现 CppCheck、PC-Lint 等和 VisualStudio 的集成,使得用起来更方便了。


引入第二个概念:CRT调试

C/C++ 编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:“最大的长处也可能成为最大的弱点”,那么 C/C++ 应用程序正好印证了这句话。在 C/C++ 应用程序开发过程中,动态分配的内存处理不当是最常见的问题。其中,最难捉摸也最难检测的错误之一就是内存泄漏,即未能正确释放以前分配的内存的错误。偶尔发生的少量内存泄漏可能不会引起我们的注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种 各样的征兆:从性能不良(并且逐渐降低)到内存完全耗尽。更糟的是,泄漏的程序可能会用掉太多内存,导致另外一个程序垮掉,而使用户无从查找问题的真正根源。此外,即使无害的内存泄漏也可能殃及池鱼。

幸运的是,Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法。下面请和我一起分享收获——如何使用 CRT 调试功能来检测内存泄漏? (传送门)

有过 MFC 开发经验的人都知道, MFC 程序如果检测到存在内存泄漏,退出程序的时候会在调试窗口提醒内存泄漏:

Detected memory leaks!
Dumping objects ->
e:\xxxxxxxxx\xxx_impl.cpp(102) : {4722} normal block at 0x00B87E10, 8 bytes long.
Data: < > 18 02 00 00 00 00 00 00

那么,如果我们不喜欢 MFC ,那么难道就没有办法?或者自己做?

其实,MFC也没有自己做。内存泄漏检测的工作是VC++的C运行库做的。也就是说,只要你是VC++程序员,都可以很方便地检测内存泄漏。

#include <crtdbg.h>

inline void EnableMemLeakCheck()
{
   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
}

void main()
{
   EnableMemLeakCheck();
   int* leak = new int[10];
}

调试后,你会发现,在 Debug 输出窗口也出现了提示:

Detected memory leaks!
Dumping objects ->
{52} normal block at 0x003C4410, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

很好,通过这样的一个设置,我们能够很直观的看出我编写的代码是否有内存泄漏了,但是,你马上会有疑问,我知道我的程序有问题, MFC 程序调试时能直接帮我们定位代码行,但是非 MFC 程序要如何定位呢?
不用着急,前面我们提到过重载 new 运算符这种方式,就是此处的关键。在 MFC 程序中,我们很容易发现如下代码:

#if defined( _DEBUG ) && ! defined( WFC_STL )
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

中间有一条明确的代码项 #define new DEBUG_NEW ,跟踪调试,你会发现:

void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)

这也正解释了 MFC 程序为何能直接定位到代码行了。我们也可以在 stdafx.h 中增加如下代码:

#ifdef _DEBUG
#define new   new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

很好,现在我们也能及时的查看到哪段代码有问题了。


_CrtDumpMemoryLeaks 和 _CrtSetBreakAlloc

  • _CrtDumpMemoryLeaks

确定自程序开始执行以来是否发生过内存泄漏,如果发生过,则转储所有已分配对象。如果已使用 _CrtSetDumpClient 安装了挂钩函数,那么,_CrtDumpMemoryLeaks每次转储 _CLIENT_BLOCK 块时,都会调用应用程序所提供的挂钩函数。

_CrtDumpMemoryLeaks() 就是显示当前的内存泄漏。 注意是“当前”,也就是说当它执行时,所有未销毁的对象均会报内存泄漏。因此尽量让这条语句在程序的最后执行。它所反映的是检测到泄漏的地方。
一般用在 MFC 中比较准确,在 InitInstance 里面调用 _CrtDumpMemoryLeaks

  • _CrtSetBreakAlloc

知道某个错误分配块的分配请求编号后,可以将该编号传递给 _CrtSetBreakAlloc 以创建一个断点
_CrtSetBreakAlloc(51); (或者直接给 _crtBreakAlloc 赋值, _crtBreakAlloc = 51; ) 这样可以快速在{51}次内存泄漏处设上断点。


另外,Visual Studio 还有专门的插件叫做 Visual Leak Detector (VLD),未使用过,但我了解应该是需要在代码中引入相关文件才能达到效果。
Intel Developer Zone 也有提供一个名为 VTune 的工具,功能非常强大,如果对非要对自己的程序极度死磕的话可以一试,使用传送门


附一、因为 std::string 是堆分配管理的,做为全局变量,它会在程序退出后再开始清理,这样为 std::string 分配的内存会被误报为内存泄漏,虽说这是一种可控制的误报,但是不方便于其它内存问题的查找。


欢迎补充!

猜你喜欢

转载自blog.csdn.net/ChaffererZ/article/details/50669686