APP下载

C/C++内存泄漏及检测

消息来源:baojiabao.com 作者: 发布时间:2024-05-04

报价宝综合消息C/C++内存泄漏及检测

“该死系统存在内存泄漏问题”,专案中由于各方面因素,总是有人抱怨存在内存泄漏,系统长时间执行之后,可用内存越来越少,甚至导致了某些服务失败。内存泄漏是最难发现的常见错误之一,因为除非用完内存或呼叫malloc失败,否则都不会导致任何问题。实际上,使用C/C++这类没有垃圾回收机制的语言时,你很多时间都花在处理如何正确释放内存上。如果程式执行时间足够长,如后台程序执行在服务器上,只要服务器不宕机就一直执行,一个小小的失误也会对程式造成重大的影响,如造成某些关键服务失败。

对于内存泄漏,本人深有体会!实习的时候,公司一个专案中就存在内存泄漏问题,专案的程式码两非常大,后台程序也比较多,造成内存泄漏的地方比较难找。这次机会是我对如何查询内存泄漏问题,有了一定的经验,后面自己的做了相关实验,在此我分享一下内存泄漏如何除错查询,主要内容如下:

1、内存泄漏简介2、Windows平台下的内存泄漏检测2.1、检测是否存在内存泄漏问题2.2、定位具体的内存泄漏地方3、Linux平台下的内存泄漏检测 4、总结其实Windows、Linux下面的内存检测都可以单独开篇详细介绍,方法和工具也远远不止文中介绍到的,我的方法也不是最优的,如果您有更好的方法,也请您告诉我和大家。

1、内存泄漏简介及后果

wikipedia中这样定义内存泄漏:在电脑科学中,内存泄漏指由于疏忽或错误造成程式未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

最难捉摸也最难检测到的错误之一是内存泄漏,即未能正确释放以前分配的内存的 bug。 只发生一次的小的内存泄漏可能不会被注意,但泄漏大量内存的程式或泄漏日益增多的程式可能会表现出各种征兆:从效能不良(并且逐渐降低)到内存完全用尽。 更糟的是,泄漏的程式可能会用掉太多内存,以致另一个程式失败,而使使用者无从查询问题的真正根源。 此外,即使无害的内存泄漏也可能是其他问题的征兆。

内存泄漏会因为减少可用内存的数量从而降低计算机的效能。最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分装置停止正常工作,或者应用程序崩溃。内存泄漏可能不严重,甚至能够被常规的手段检测出来。在现代操作系统中,一个应用程序使用的常规内存在程式终止时被释放。这表示一个短暂执行的应用程序中的内存泄漏不会导致严重后果。

在以下情况,内存泄漏导致较严重的后果:

程式执行后置之不理,并且随着时间的流失消耗越来越多的内存(比如服务器上的后台任务,尤其是嵌入式系统中的后台任务,这些任务可能被执行后很多年内都置之不理);新的内存被频繁地分配,比如当显示电脑游戏或动画视讯画面时;程式能够请求未被释放的内存(比如共享内存),甚至是在程式终止的时候;泄漏在操作系统内部发生;泄漏在系统关键驱动中发生;内存非常有限,比如在嵌入式系统或便携装置中;当运行于一个终止时内存并不自动释放的操作系统(比如AmigaOS)之上,而且一旦丢失只能通过重启来恢复。下面我们通过以下例子来介绍如何检测内存泄漏问题:

#include

#include

using namespace std;

void GetMemory(char *p, int num)

{

p = (char*)malloc(sizeof(char) * num);//使用new也能够检测出来

}

int main(int argc,char** argv)

{

char *str = NULL;

GetMemory(str, 100);

cout

//如果main中存在while循环呼叫GetMemory

//那么问题将变得很严重

//while(1){GetMemory(...);}

return 0;

}

实际中不可能这么简单,如果这么简单也用不着别的方法,程序员一眼就可以看出问题,此程式只用于测试。

2、Windows平台下的内存泄漏检测

2.1、检测是否存在内存泄漏问题

Windows平台下面Visual Studio 侦错程式和 C 执行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法,原理大致如下:内存分配要通过CRT在执行时实现,只要在分配内存和释放内存时分别做好记录,程式结束时对比分配内存和释放内存的记录就可以确定是不是有内存泄漏。在vs中启用内存检测的方法如下:

STEP1,在程式中包括以下语句: (#include 语句必须采用上文所示顺序。 如果更改了顺序,所使用的函式可能无法正常工作。)#define _CRTDBG_MAP_ALLOC

#include

#include

通过包括 crtdbg.h,将 malloc 和 free 函式对映到它们的除错版本,即 _malloc_dbg 和 _free_dbg,这两个函式将跟踪内存分配和释放。 此对映只在除错版本(在其中定义了_DEBUG)中发生。 释出版本使用普通的 mallocfree 函式。

#define 语句将 CRT 堆函式的基版本对映到对应的“Debug”版本。 并非绝对需要该语句;但如果没有该语句,内存泄漏转储包含的有用资讯将较少。

STEP2, 在添加了上述语句之后,可以通过在程式中包括以下语句(通常应恰好放在程式退出位置之前)来转储内存泄漏资讯:_CrtDumpMemoryLeaks();

此时,完整的程式码如下:

+ View Code

当在侦错程式下执行程式时,_CrtDumpMemoryLeaks 将在“输出”视窗中显示内存泄漏资讯。 内存泄漏资讯如下所示:

如果没有使用 #define _CRTDBG_MAP_ALLOC 语句,内存泄漏转储将如下所示:

未定义 _CRTDBG_MAP_ALLOC 时,所显示的会是:

内存分配编号(在大括号内)。块型别(普通、客户端或 CRT)。“普通块”是由程式分配的普通内存。“客户端块”是由 MFC 程式用于需要解构函式的物件的特殊型别内存块。 MFC new 操作根据正在建立的物件的需要建立普通块或客户端块。“CRT 块”是由 CRT 库为自己使用而分配的内存块。 CRT 库处理这些块的释放,因此您不大可能在内存泄漏报告中看到这些块,除非出现严重错误(例如 CRT 库损坏)。从不会在内存泄漏资讯中看到下面两种块型别:

“可用块”是已释放的内存块。“忽略块”是您已特别标记的块,因而不出现在内存泄漏报告中。十六进位制形式的内存位置。以字节为单位的块大小。前 16 字节的内容(亦为十六进位制)。定义了 _CRTDBG_MAP_ALLOC 时,还会显示在其中分配泄漏的内存的档案。 档名后括号中的数字(本示例中为 10)是该档案中的行号。

注意:如果程式总是在同一位置退出,呼叫 _CrtDumpMemoryLeaks 将非常容易。 如果程式从多个位置退出,则无需在每个可能退出的位置放置对_CrtDumpMemoryLeaks 的呼叫,而可以在程式开始处包含以下呼叫:

+ View Code

该语句在程式退出时自动呼叫 _CrtDumpMemoryLeaks。 必须同时设定 _CRTDBG_ALLOC_MEM_DF_CRTDBG_LEAK_CHECK_DF 两个位域,如前面所示。

2.2、定位具体的内存泄漏地方

通过上面的方法,我们几乎可以定位到是哪个地方呼叫内存分配函式malloc和new等,如上例中的GetMemory函式中,即第10行!但是不能定位到,在哪个地方呼叫GetMemory()导致的内存泄漏,而且在大型专案中可能有很多处呼叫GetMemory。如何要定位到在哪个地方呼叫GetMemory导致的内存泄漏?

定位内存泄漏的另一种技术涉及在关键点对应用程序的内存状态拍快照。 CRT 库提供一种结构型别 _CrtMemState,您可用它储存内存状态的快照:

_CrtMemState s1, s2, s3;

若要在给定点对内存状态拍快照,请向 _CrtMemCheckpoint 函式传递 _CrtMemState 结构。 该函式用当前内存状态的快照填充此结构:

_CrtMemCheckpoint( &s1 );

通过向 _CrtMemDumpStatistics 函式传递 _CrtMemState 结构,可以在任意点转储该结构的内容:

_CrtMemDumpStatistics( &s1 );

若要确定程式码中某一部分是否发生了内存泄漏,可以在该部分之前和之后对内存状态拍快照,然后使用 _CrtMemDifference 比较这两个状态:

_CrtMemCheckpoint( &s1 );

// memory allocations take place here

_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )

_CrtMemDumpStatistics( &s3 );

顾名思义,_CrtMemDifference 比较两个内存状态(s1 和 s2),生成这两个状态之间差异的结果(s3)。 在程式的开始和结尾放置 _CrtMemCheckpoint 呼叫,并使用_CrtMemDifference 比较结果,是检查内存泄漏的另一种方法。 如果检测到泄漏,则可以使用 _CrtMemCheckpoint 呼叫通过二进位制搜寻技术来划分程式和定位泄漏。

如上面的例子程式我们可以这样来定位确切的呼叫GetMemory的地方:

+ View Code

除错时,程式输出如下结果:

这说明在s1和s2之间存在内存泄漏!!!如果GetMemory不是在s1和s2之间呼叫,那么就不会有资讯输出。

有爱好C++程式设计的小伙伴看过来啦:想学习程式设计的小伙伴们可以转发+关注+私信回复:“资料”就可以拿到一份我为大家准备的C++程式设计学习资料(C/C++高阶开发/Linux 服务器架构/ 大型互联网应用/分散式/高并发/大资料等资料)

3、Linux平台下的内存泄漏检测

在上面我们介绍了,vs中在程式码中“包含crtdbg.h,将 malloc 和 free 函式对映到它们的除错版本,即 _malloc_dbg 和 _free_dbg,这两个函式将跟踪内存分配和释放。 此对映只在除错版本(在其中定义了_DEBUG)中发生。 释出版本使用普通的 mallocfree 函式。”即为malloc和free做了钩子,用于记录内存分配资讯。

Linux下面也有原理相同的方法——mtrace,http://en.wikipedia.org/wiki/Mtrace。方法类似,我这就不具体描述,参加给出的连结。这节我主要介绍一个非常强大的工具valgrind。如下图所示:

如上图所示知道:

==6118== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1

==6118== at 0x4024F20: malloc (vg_replace_malloc.c:236)

==6118== by 0x8048724: GetMemory(char*, int) (in /home/netsky/workspace/a.out)

==6118== by 0x804874E: main (in /home/netsky/workspace/a.out)

是在main中呼叫了GetMemory导致的内存泄漏,GetMemory中是呼叫了malloc导致泄漏了100字节的内存。

Things to notice:

• There is a lot of information in each error message; read it carefully.

• The 6118 is the process ID; it’s usually unimportant.

• The first line ("Heap Summary") tells you what kind of error it is.

• Below the first line is a stack trace telling you where the problem occurred. Stack traces can get quite large, and be

confusing, especially if you are using the C++ STL. Reading them from the bottom up can help.

• The code addresses (eg. 0x4024F20) are usually unimportant, but occasionally crucial for tracking down weirder

bugs.

The stack trace tells you where the leaked memory was allocated. Memcheck cannot tell you why the memory leaked,

unfortunately. (Ignore the "vg_replace_malloc.c", that’s an implementation detail.)

There are several kinds of leaks; the two most important categories are:

• "definitely lost": your program is leaking memory -- fix it!

• "probably lost": your program is leaking memory, unless you’re doing funny things with pointers (such as moving

them to point to the middle of a heap block)

Valgrind的使用请见手册http://valgrind.org/docs/manual/manual.html。

4、总结

其实内存泄漏的原因可以概括为:呼叫了malloc/new等内存申请的操作,但缺少了对应的free/delete,总之就是,malloc/new比free/delete的数量多。我们在程式设计时需要注意这点,保证每个malloc都有对应的free,每个new都有对应的deleted!!!平时要养成这样一个好的习惯。

要避免内存泄漏可以总结为以下几点:

程序员要养成良好习惯,保证malloc/new和free/delete匹配;检测内存泄漏的关键原理就是,检查malloc/new和free/delete是否匹配,一些工具也就是这个原理。要做到这点,就是利用宏或者钩子,在使用者程式与执行库之间加了一层,用于记录内存分配情况。

有爱好C++程式设计的小伙伴看过来啦:想学习程式设计的小伙伴们可以转发+关注+私信回复:“资料”就可以拿到一份我为大家准备的C++程式设计学习资料(C/C++高阶开发/Linux 服务器架构/ 大型互联网应用/分散式/高并发/大资料等资料)

2020-02-01 03:58:00

相关文章