天天看點

檢測記憶體洩露的方法

轉載自:http://hi.baidu.com/cbzywabhmsdinre/item/ad7d2abf573d0cd285dd795a

C/C++ 程式設計語言的最強大功能之一便是其動态配置設定和釋放記憶體,但是中國有句古話:“最大的長處也可能成為最大的弱點”,那麼 C/C++ 應用程式正好印證了這句話。在 C/C++ 應用程式開發過程中,動态配置設定的記憶體處理不當是最常見的問題。其中,最難捉摸也最難檢測的錯誤之一就是記憶體洩漏,即未能正确釋放以前配置設定的記憶體的錯誤。偶爾發生的少量記憶體洩漏可能不會引起我們的注意,但洩漏大量記憶體的程式或洩漏日益增多的程式可能會表現出各種 各樣的征兆:從性能不良(并且逐漸降低)到記憶體完全耗盡。更糟的是,洩漏的程式可能會用掉太多記憶體,導緻另外一個程式垮掉,而使使用者無從查找問題的真正根源。此外,即使無害的記憶體洩漏也可能殃及池魚。

  幸運的是,Visual Studio 調試器和 C 運作時 (CRT) 庫為我們提供了檢測和識别記憶體洩漏的有效方法。下面請和我一起分享收獲——如何使用 CRT 調試功能來檢測記憶體洩漏?

一、如何啟用記憶體洩漏檢測機制

  VC++ IDE 的預設狀态是沒有啟用記憶體洩漏檢測機制的,也就是說即使某段代碼有記憶體洩漏,調試會話的 Output 視窗的 Debug 頁不會輸出有關記憶體洩漏資訊。你必須設定兩個最基本的機關來啟用記憶體洩漏檢測機制。

  一是使用調試堆函數:

#define _CRTDBG_MAP_ALLOC

#include<stdlib.h>

#include<crtdbg.h>

  注意:#include 語句的順序。如果更改此順序,所使用的函數可能無法正确工作。

  通過包含 crtdbg.h 頭檔案,可以将 malloc 和 free 函數映射到其“調試”版本 _malloc_dbg 和 _free_dbg,這些函數會跟蹤記憶體配置設定和釋放。此映射隻在調試(Debug)版本(也就是要定義 _DEBUG)中有效。發行版本(Release)使用普通的 malloc 和 free 函數。#define 語句将 CRT 堆函數的基礎版本映射到對應的“調試”版本。該語句不是必須的,但如果沒有該語句,那麼有關記憶體洩漏的資訊會不全。

  二是在需要檢測記憶體洩漏的地方添加下面這條語句來輸出記憶體洩漏資訊:

_CrtDumpMemoryLeaks();

  當在調試器下運作程式時,_CrtDumpMemoryLeaks 将在 Output 視窗的 Debug 頁中顯示記憶體洩漏資訊。比如: Detected memory leaks!

Dumping objects ->

C:\Temp\memleak\memleak.cpp(15) : {45} normal block at 0x00441BA0, 2 bytes long.

Data: <AB> 41 42

c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {44} normal

block at 0x00441BD0, 33 bytes long.

Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD

c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {43} normal

block at 0x00441C20, 40 bytes long.

Data: < C > 08 02 43 00 16 00 00 00 00 00 00 00 00 00 00 00

Object dump complete.

  如果不使用 #define _CRTDBG_MAP_ALLOC 語句,記憶體洩漏的輸出是這樣的:

Detected memory leaks!

Dumping objects ->

{45} normal block at 0x00441BA0, 2 bytes long.

Data: <AB> 41 42

{44} normal block at 0x00441BD0, 33 bytes long.

Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD

{43} normal block at 0x00441C20, 40 bytes long.

Data: < C > C0 01 43 00 16 00 00 00 00 00 00 00 00 00 00 00

Object dump complete.

  根據這段輸出資訊,你無法知道在哪個源程式檔案裡發生了記憶體洩漏。下面我們來研究一下輸出資訊的格式。第一行和第二行沒有什麼可說的,從第三行開始:

xx}:花括弧内的數字是記憶體配置設定序号,本文例子中是 {45},{44},{43};

block:記憶體塊的類型,常用的有三種:normal(普通)、client(用戶端)或 CRT(運作時);本文例子中是:normal block;

用十六進制格式表示的記憶體位置,如:at 0x00441BA0 等;

以位元組為機關表示的記憶體塊的大小,如:32 bytes long;

前 16 位元組的内容(也是用十六進制格式表示),如:Data: 41 42 等;

  仔細觀察不難發現,如果定義了 _CRTDBG_MAP_ALLOC ,那麼在記憶體配置設定序号前面還會顯示在其中配置設定洩漏記憶體的檔案名,以及檔案名後括号中的數字表示發生洩漏的代碼行号,比如:

C:\Temp\memleak\memleak.cpp(15)

  輕按兩下 Output 視窗中此檔案名所在的輸出行,便可跳到源程式檔案配置設定該記憶體的代碼行(也可以選中該行,然後按 F4,效果一樣) ,這樣一來我們就很容易定位記憶體洩漏是在哪裡發生的了,是以,_CRTDBG_MAP_ALLOC 的作用顯而易見。

使用 _CrtSetDbgFlag

  如果程式隻有一個出口,那麼調用 _CrtDumpMemoryLeaks 的位置是很容易選擇的。但是,如果程式可能會在多個地方退出該怎麼辦呢?在每一個可能的出口處調用 _CrtDumpMemoryLeaks 肯定是不可取的,那麼這時可以在程式開始處包含下面的調用:_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );這條語句無論程式在什麼地方退出都會自動調用 _CrtDumpMemoryLeaks。注意:這裡必須同時設定兩個位域标志:_CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF。

設定 CRT 報告模式

  預設情況下,_CrtDumpMemoryLeaks 将記憶體洩漏資訊 dump 到 Output 視窗的 Debug 頁, 如果你想将這個輸出定向到别的地方,可以使用 _CrtSetReportMode 進行重置。如果你使用某個庫,它可能将輸出定向到另一位置。此時,隻要使用以下語句将輸出位置設回 Output 視窗即可:

_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );

  有關使用 _CrtSetReportMode 的詳細資訊,請參考 MSDN 庫關于 _CrtSetReportMode 的描述。

Visual Leak Detector是一款用于Visual C++的免費的記憶體洩露檢測工具。可以在http://www.codeproject.com/tools/visualleakdetector.asp 下載下傳到。相比較其它的記憶體洩露檢測工具,它在檢測到記憶體洩漏的同時,還具有如下特點:

1、  可以得到記憶體洩漏點的調用堆棧,如果可以的話,還可以得到其所在檔案及行号;

2、  可以得到洩露記憶體的完整資料;

3、  可以設定記憶體洩露報告的級别;

4、  它是一個已經打包的lib,使用時無須編譯它的源代碼。而對于使用者自己的代碼,也隻需要做很小的改動;

5、  他的源代碼使用GNU許可釋出,并有詳盡的文檔及注釋。對于想深入了解堆記憶體管理的讀者,是一個不錯的選擇。

       可見,從使用角度來講,Visual Leak Detector簡單易用,對于使用者自己的代碼,唯一的修改是#include Visual Leak Detector的頭檔案後正常運作自己的程式,就可以發現記憶體問題。從研究的角度來講,如果深入Visual Leak Detector源代碼,可以學習到堆記憶體配置設定與釋放的原理、記憶體洩漏檢測的原理及記憶體操作的常用技巧等。

下面讓我們來介紹如何使用這個小巧的工具。

       首先從網站上下載下傳zip包,解壓之後得到vld.h, vldapi.h, vld.lib, vldmt.lib, vldmtdll.lib, dbghelp.dll等檔案。将.h檔案拷貝到Visual C++的預設include目錄下,将.lib檔案拷貝到Visual C++的預設lib目錄下,便安裝完成了。因為版本問題,如果使用windows 2000或者以前的版本,需要将dbghelp.dll拷貝到你的程式的運作目錄下,或其他可以引用到的目錄。

       接下來需要将其加入到自己的代碼中。方法很簡單,隻要在包含入口函數的.cpp檔案中包含vld.h就可以。如果這個cpp檔案包含了stdafx.h,則将包含vld.h的語句放在stdafx.h的包含語句之後,否則放在最前面。如下是一個示例程式:

#include <vld.h>

void main()

{

}

       接下來讓我們來示範如何使用Visual Leak Detector檢測記憶體洩漏。下面是一個簡單的程式,用new配置設定了一個int大小的堆記憶體,并沒有釋放。其申請的記憶體位址用printf輸出到螢幕上。

#include <vld.h>

#include <stdlib.h>

#include <stdio.h>

void f()

{

    int *p = new int(0x12345678);

    printf("p=%08x, ", p);

}

void main()

{

    f();

}

編譯運作後,在标準輸出視窗得到:

p=003a89c0

在Visual C++的Output視窗得到:

WARNING: Visual Leak Detector detected memory leaks!

---------- Block 57 at 0x003A89C0: 4 bytes ----------  --57号塊0x003A89C0位址洩漏了4個位元組

  Call Stack:                                               --下面是調用堆棧

    d:\test\testvldconsole\testvldconsole\main.cpp (7): f  --表示在main.cpp第7行的f()函數

    d:\test\testvldconsole\testvldconsole\main.cpp (14): main –輕按兩下以引導至對應代碼處

    f:\rtm\vctools\crt_bld\self_x86\crt\src\crtexe.c (586): __tmainCRTStartup

    f:\rtm\vctools\crt_bld\self_x86\crt\src\crtexe.c (403): mainCRTStartup

    0x7C816D4F (File and line number not available): RegisterWaitForInputIdle

  Data:                                   --這是洩漏記憶體的内容,0x12345678

    78 56 34 12                                                  xV4..... ........

Visual Leak Detector detected 1 memory leak.    

第二行表示57号塊有4位元組的記憶體洩漏,位址為0x003A89C0,根據程式控制台的輸出,可以知道,該位址為指針p。程式的第7行,f()函數裡,在該位址處配置設定了4位元組的堆記憶體空間,并指派為0x12345678,這樣在報告中,我們看到了這4位元組同樣的内容。

可以看出,對于每一個記憶體洩漏,這個報告列出了它的洩漏點、長度、配置設定該記憶體時的調用堆棧、和洩露記憶體的内容(分别以16進制和文本格式列出)。輕按兩下該堆棧報告的某一行,會自動在代碼編輯器中跳到其所指檔案的對應行。這些資訊對于我們查找記憶體洩露将有很大的幫助。

這是一個很友善易用的工具,安裝後每次使用時,僅僅需要将它頭檔案包含進來重新build就可以。而且,該工具僅在build Debug版的時候會連接配接到你的程式中,如果build Release版,該工具不會對你的程式産生任何性能等方面影響。是以盡可以将其頭檔案一直包含在你的源代碼中。