1. 程式人生 > >VLD記憶體洩露庫的使用

VLD記憶體洩露庫的使用

VLD簡介
由於C/C++語言沒有所謂的垃圾收集器,記憶體的分配和釋放都需要程式設計師自己來控制,這會給C/C++程式設計師帶來一定的困難。當您的程式越來越複雜時,它的記憶體管理也會變得越來越困難。記憶體洩漏、記憶體越界是最常見的記憶體問題之一。
記憶體洩漏如果不是很嚴重的話,在短時間內對程式不會造成太大的影響,而且在程序終止的時候,所有分配的記憶體都會釋放掉。但是對於長時間執行的程式,其破壞力是驚人的,從效能下降到記憶體耗盡,甚至會影響到其它程式的正常執行。
此外,記憶體問題存在一個共同的特點,它本身並不會有很明顯的現象,當有異常出現時就很難檢查問題的原因所在,這給除錯記憶體問題帶來了很大的難度。
VLD是一款用於VisualC++的免費記憶體洩漏檢查工具。可以在codeproject.com網站上找到,相比其它的記憶體洩漏哦給你根據,他在檢查記憶體洩漏的同事,還具有如下特點:
1) 可以得到記憶體洩漏點的呼叫堆疊,如果可以的話,還可以得到其所在的檔案及行號;
2) 可以得到洩漏記憶體的完整資料;
3) 惡意設定記憶體洩漏報告的級別;
4) 它以動態庫的形式提供,無需編譯原始碼,只需要很小的改動程式;
5) 原始碼使用GNU許可釋出,並有詳細的文件及其註釋。
從使用的角度講,VLD簡單易用,對於使用者自己的程式碼中唯一需要修改的地方是#include VLD的標頭檔案後正常執行自己的程式就可以發現記憶體問題。從研究角度上講,如果輸入到VLD原始碼,可以學習到堆記憶體分片與釋放的原理、記憶體檢查的原理機器記憶體操作的常用技巧等。
VLD使用
VLD網址:

http://vld.codeplex.com/
http://www.codeproject.com/Articles/9815/Visual-Leak-Detector-Enhanced-Memory-Leak-Detectio
下載Visual LeakDetector,當前版本2.2.3,開啟Visual C++ IDE的”工具”→”選項”→”專案和解決方案”→”VC++ 目錄”,在”包含檔案”中增加VLD的標頭檔案路徑”\include”路徑,在”庫檔案”增加VLD庫檔案的”\lib\Win32”路徑,此外動態庫的”\bin\Win32”路徑在安裝時已經新增到環境變數裡面了,若是未新增,則需要手動拷貝”\bin\Win32”下的檔案到可執行檔案所在的目錄中(拷貝的檔案有dbghelp.dll/Microsoft.DTfW.DHL.manifest/vld_x86.dll/vld.ini)。
接下來需要將VLD加入到自己的程式碼中。方法很簡單,只要在包含入口函式的.cpp檔案中包含vld.h就可以。如果這個cpp檔案中包含了stdafx.h,則將包含vld.h的語句放在stdafx.h的包含語句之後,否則放在最前面。
示例程式:

#include<vld.h>                 // 包含VLD的標頭檔案
#include<stdlib.h>
#include<stdio.h>
void f()
{
    int *p = new int(0x12345678);
    printf("p=%08x, ", p);
}
int main()
{
    f();
    return 0;
}
注:VLD只能在Windows下使用,在包含vld.h標頭檔案時增加預編譯選項。
注:在Release模式下,不會連結VisualLeak Detector。
注:Visual LeakDetector有一些配置項,可以設定記憶體洩露報告的儲存地(檔案、偵錯程式),拷貝"\Visual Leak Detector"路徑下的vld.ini檔案到執行檔案所在的目錄下(在IDE執行的話,則需要拷貝到工程目錄下),修改以下項:

ReportFile =.\memory_leak_report.txt
ReportTo = both
VLD工具原理
下面我們來看看VLD是如何工作的。在VisualC++中內建工具CRT Debug Heap工具,在使用Debug版本分配記憶體時,它會在記憶體塊中記錄分配該記憶體的檔名和行號。當程式退出時CRT會在main函式返回時做一些清理工作,此時檢查除錯堆記憶體,如果仍然有記憶體沒釋放,則一定存在記憶體洩漏問題。從這些沒有被釋放的記憶體塊的頭中可以得到檔名和行號。這種靜態的方法可以檢查出記憶體洩漏,但是不知道洩漏究竟是怎麼發生的,也不知道該記憶體分配語句是如何被執行到的,想要了解這些必須對記憶體分配過程進行動態跟蹤。VLD就是這樣做的,在每次記憶體分配的時候記錄其上下文,當程式退出時對檢測到的記憶體洩漏查詢其上下文資訊,並轉換成報告輸出到Output中。
初始化
VLD要記錄每次的記憶體分配,它通過Windows提供的分配鉤子allocation hooks來監視除錯堆記憶體的分配。它是一個使用者自定義的回撥函式,在每次從堆中分配記憶體之前被呼叫,在初始化是VLD使用_CrtSetAllocation註冊這個鉤子函式。
全域性變數在程式初始化時就初始化,如果將VLD作為一個全域性變數就可以與程式一起啟動,但是C/C++並沒有約定全域性變數初始化的順序,如果其它全域性變數的建構函式中有記憶體分配則可能無法檢測到。因此,VLD使用C/C++提供的#pragma init_seg來減少其它全域性變數在它之前進行初始化。根據#pragma init_seg的定義,全域性變數初始化分為3個階段,首先是compiler階段,一般進行C語言執行時庫的初始化;然後是lib段,一般用於第三方類庫的初始化扽;最後是user段,大部分的初始化都在這個階段進行。
記錄記憶體分配
一個記憶體分配鉤子函式需要具有如下的定義:
int AllocHook(int allocType, void*userData, size_t size,int blockType, long requestNumber, onst unsigned char*filename, int lineNumber);
該函式需要在VLD初始化時被註冊,每次從堆中分配記憶體前被呼叫,它需要處理的事情就是記錄下此時的呼叫堆疊和此時堆記憶體分配的唯一標識requestNumber。
得到當前堆疊的二進位制表示並不是很複雜的事情,但是因為不同的體系結構、不同的編譯器、不同的作業系統所產生的堆疊內容是不一樣的,要解釋堆疊並得到整個函式的呼叫過程比較複雜。不過Windows提供了一個StackWalk64函式可以獲得堆疊的內容。
VLD是常用的C/C++記憶體洩漏檢查工具,可以在ViusalC++中使用,在Viusal Studio 2008和2010中使用需要注意兩點:
1) 版本問題:VLD已經更新到2.2版本,修正了許多bug,而且在2010版本下工作良好,VisualC++ 6.0推薦使用1.0版本,1.9b版本不是很穩定不建議使用,2.2版本的下載網址為http://vld.codeplex.com.
2) 設定變化:VC++Directories設定已經變化位置,在2010中設定過程如下:
View | Other Window | Property Manager
Go to “VC++ Directories” settings
Set include folder path
Set lib folder path
點OK,我們就設定好了include和lib目錄。
使用問題
問題1:VLD 1.9
在vista下使用vld的使用,總是出現錯誤無法正常工作,後來經過搜尋,在http://www.codeproject.com/KB/applications/visualleakdetector.aspx
上的評論中找到了解決的方法:
評論“Solution forrunning 1.9 beta on Visual Studio 2008 with Vista ”給出瞭解決方法:
評論1:
VLD keptcrashing when trying to use 1.9g beta on Windows Vista, visual studio 2008. Itried all the suggestions on here and nothing worked. But I finally figured itout.
when you make a project in visual C++ 2008,it sets some strange advanced Linker properties that cause VLD to crash:
I changedLinker->Advanced->Randomized Base Address from Enable Image Randomization(/DYNAMICBASE) to Disable Image Randomization (/DYNAMICBASE:NO)
Then I changed Linker->Advanced->DataExecution Prevention from Image is compatible with DEP (/NXCOMPAT) to Default
And now it works perfectly
Please let me know if this helped you!It’ll make me feel better for spending a whole day trying to get it working!
-Nadav
評論2:
The base address randomization seems to benot necessary. Just disable DEP.
大致的意思是說,只需要禁用DEP即可,
在工程的“屬性”->“連結器”->“高階”->資料執行保護(DEP),設為“預設”(default)或者“映像與 DEP 不相容(/NXCOMPAT:NO)“ 即可。(修改後好像不可用)。
注:這個選項只針對Vista有效!!!
問題2:VLD 2.2.3
在專案中使用了visual leak detector,除錯時程式無法啟動報錯“應用程式正常啟動失敗(0xc0150002)”。
解決流程:
檢視vs輸出資訊最後一條是:
Theprogram ‘[3980] MobileSignalAnalyzer.exe: Native’ has exited with code-1072365566 (0xc0150002)
在網上多方查詢有:
http://blog.csdn.net/evilswords/article/details/5698851
http://blog.csdn.net/brook0344/article/details/6685724
這兩篇有解決辦法,就是把VLD中的這兩個複製到執行資料夾下就正常了
Microsoft.VC90.CRT.manifest
Microsoft.DTfW.DHL.manifest
產生原因:
VC2003、VC2005、VC2008及其後續版本,對底層最基本的CRT、MFC、ATL庫都進行了重構,為了避免不同版本的庫引起衝突,重構後的庫檔案一般放在C://windows/WinSxS 資料夾中,並用特定的資料夾/檔名稱進行標識;
與VC6不同, VC2003、VC2005、VC2008及其後續版本,引入了manifest清單的概念,即應用程式編譯後會同時生成對應的.manifest檔案,並將該.manifest檔案作為資源編譯到dll或者exe中去。.manifest檔案實際上是一個XML格式的文字檔案,裡面記錄了dll或exe中要引用的CRT、MFC、ATL庫的版本和名稱。VC6編譯的應用程式對CRT、MFC、ATL的dll都是直接呼叫,而VC2003、VC2005、VC2008編譯的程式都是先查詢編譯到資源中的manifest中的記錄,然後按照記錄提供的版本和名稱去搜尋對應的CRT、MFC、ATL庫以及隨庫釋出的.manifest檔案,搜尋的路徑包括當前目錄、C://windows/WinSxS等等,如果沒有找到對應的庫檔案,則提示“應用程式正常初始化失敗”。