https://www.ibm.com/developerworks/cn/linux/l-cn-valgrind/html
Valgrind是一套Linux下,開放源代碼(GPL V2)的仿真調試工具的集合。Valgrind由內核(core)以及基於內核的其餘調試工具組成。內核相似於一個框架(framework),它模擬了一個CPU環境,並提供服務給其餘工具;而其餘工具則相似於插件 (plug-in),利用內核提供的服務完成各類特定的內存調試任務。Valgrind的體系結構以下圖所示:node
Valgrind包括以下一些工具:linux
要發現Linux下的內存問題,首先必定要知道在Linux下,內存是如何被分配的?下圖展現了一個典型的Linux C程序內存空間佈局:編程
一個典型的Linux C程序內存空間由以下幾部分組成:數組
Memcheck檢測內存問題的原理以下圖所示:緩存
Memcheck 可以檢測出內存問題,關鍵在於其創建了兩個全局表。多線程
對於進程的整個地址空間中的每個字節(byte),都有與之對應的 8 個 bits;對於 CPU 的每一個寄存器,也有一個與之對應的 bit 向量。這些 bits 負責記錄該字節或者寄存器值是否具備有效的、已初始化的值。框架
對於進程整個地址空間中的每個字節(byte),還有與之對應的 1 個 bit,負責記錄該地址是否可以被讀寫。函數
檢測原理:工具
第一步:準備好程序
爲了使valgrind發現的錯誤更精確,如可以定位到源代碼行,建議在編譯時加上-g參數,編譯優化選項請選擇O0,雖然這會下降程序的執行效率。
這裏用到的示例程序文件名爲:sample.c(以下所示),選用的編譯器爲gcc。
生成可執行程序 gcc –g –O0 sample.c –o sample
第二步:在valgrind下,運行可執行程序。
利用valgrind調試內存問題,不須要從新編譯源程序,它的輸入就是二進制的可執行程序。調用Valgrind的通用格式是:valgrind [valgrind-options] your-prog [your-prog-options]
Valgrind 的參數分爲兩類,一類是 core 的參數,它對全部的工具都適用;另一類就是具體某個工具如 memcheck 的參數。Valgrind 默認的工具就是 memcheck,也能夠經過「--tool=tool name」指定其餘的工具。Valgrind 提供了大量的參數知足你特定的調試需求,具體可參考其用戶手冊。
這個例子將使用 memcheck,因而能夠輸入命令入下:valgrind <Path>/sample.
第三步:分析 valgrind 的輸出信息。
如下是運行上述命令後的輸出。
示例程序顯然有兩個問題,一是fun函數中動態申請的堆內存沒有釋放;二是對堆內存的訪問越界。這兩個問題均被valgrind發現。
在Linux平臺開發應用程序時,最常碰見的問題就是錯誤的使用內存,咱們總結了常見了內存錯誤使用狀況,並說明了如何用valgrind將其檢測出來。
問題分析:
對於位於程序中不一樣段的變量,其初始值是不一樣的,全局變量和靜態變量初始值爲0,而局部變量和動態申請的變量,其初始值爲隨機值。若是程序使用了爲隨機值的變量,那麼程序的行爲就變得不可預期。
下面的程序就是一種常見的,使用了未初始化的變量的狀況。數組a是局部變量,其初始值爲隨機值,而在初始化時並無給其全部數組成員初始化,如此在接下來使用這個數組時就潛在有內存問題。
結果分析:
假設這個文件名爲:badloop.c,生成的可執行程序爲badloop。用memcheck對其進行測試,輸出以下。
輸出結果顯示,在該程序第11行中,程序的跳轉依賴於一個未初始化的變量。準確的發現了上述程序中存在的問題。
問題分析:
這種狀況是指:訪問了你不該該/沒有權限訪問的內存地址空間,好比訪問數組時越界;對動態內存訪問時超出了申請的內存大小範圍。下面的程序就是一個典型的數組越界問題。pt是一個局部數組變量,其大小爲4,p初始指向pt數組的起始地址,但在對p循環疊加後,p超出了pt數組的範圍,若是此時再對p進行寫操做,那麼後果將不可預期。
結果分析:
假設這個文件名爲badacc.cpp,生成的可執行程序爲badacc,用memcheck對其進行測試,輸出以下。
輸出結果顯示,在該程序的第15行,進行了非法的寫操做;在第16行,進行了非法讀操做。準確地發現了上述問題。
問題分析:
C 語言的強大和可怕之處在於其能夠直接操做內存,C 標準庫中提供了大量這樣的函數,好比 strcpy, strncpy, memcpy, strcat 等,這些函數有一個共同的特色就是須要設置源地址 (src),和目標地址(dst),src 和 dst 指向的地址不能發生重疊,不然結果將不可預期。
下面就是一個 src 和 dst 發生重疊的例子。在 15 與 17 行中,src 和 dst 所指向的地址相差 20,但指定的拷貝長度倒是 21,這樣就會把以前的拷貝值覆蓋。第 24 行程序相似,src(x+20) 與 dst(x) 所指向的地址相差 20,但 dst 的長度卻爲 21,這樣也會發生內存覆蓋。
結果分析:
假設這個文件名爲 badlap.cpp,生成的可執行程序爲 badlap,用 memcheck 對其進行測試,輸出以下。
輸出結果顯示上述程序中第15,17,24行,源地址和目標地址設置出現重疊。準確的發現了上述問題。
問題分析:
常見的內存分配方式分三種:靜態存儲,棧上分配,堆上分配。全局變量屬於靜態存儲,它們是在編譯時就被分配了存儲空間,函數內的局部變量屬於棧上分配,而最靈活的內存使用方式當屬堆上分配,也叫作內存動態分配了。經常使用的內存動態分配函數包括:malloc, alloc, realloc, new等,動態釋放函數包括free, delete。
一旦成功申請了動態內存,咱們就須要本身對其進行內存管理,而這又是最容易犯錯誤的。下面的一段程序,就包括了內存動態管理中常見的錯誤。
常見的內存動態管理錯誤包括:
因爲 C++ 兼容 C,而 C 與 C++ 的內存申請和釋放函數是不一樣的,所以在 C++ 程序中,就有兩套動態內存管理函數。一條不變的規則就是採用 C 方式申請的內存就用 C 方式釋放;用 C++ 方式申請的內存,用 C++ 方式釋放。也就是用 malloc/alloc/realloc 方式申請的內存,用 free 釋放;用 new 方式申請的內存用 delete 釋放。在上述程序中,用 malloc 方式申請了內存卻用 delete 來釋放,雖然這在不少狀況下不會有問題,但這絕對是潛在的問題。
申請了多少內存,在使用完成後就要釋放多少。若是沒有釋放,或者少釋放了就是內存泄露;多釋放了也會產生問題。上述程序中,指針p和pt指向的是同一塊內存,卻被前後釋放兩次。
本質上說,系統會在堆上維護一個動態內存鏈表,若是被釋放,就意味着該塊內存能夠繼續被分配給其餘部分,若是內存被釋放後再訪問,就可能覆蓋其餘部分的信息,這是一種嚴重的錯誤,上述程序第16行中就在釋放後仍然寫這塊內存。
結果分析:
假設這個文件名爲badmac.cpp,生成的可執行程序爲badmac,用memcheck對其進行測試,輸出以下。
輸出結果顯示,第14行分配和釋放函數不一致;第16行發生非法寫操做,也就是往釋放後的內存地址寫值;第17行釋放內存函數無效。準確地發現了上述三個問題。
問題描述:
內存泄露(Memory leak)指的是,在程序中動態申請的內存,在使用完後既沒有釋放,又沒法被程序的其餘部分訪問。內存泄露是在開發大型程序中最使人頭疼的問題,以致於有人說,內存泄露是沒法避免的。其實否則,防止內存泄露要從良好的編程習慣作起,另外重要的一點就是要增強單元測試(Unit Test),而memcheck就是這樣一款優秀的工具。
下面是一個比較典型的內存泄露案例。main函數調用了mk函數生成樹結點,但是在調用完成以後,卻沒有相應的函數:nodefr釋放內存,這樣內存中的這個樹結構就沒法被其餘部分訪問,形成了內存泄露。
在一個單獨的函數中,每一個人的內存泄露意識都是比較強的。但不少狀況下,咱們都會對malloc/free 或new/delete作一些包裝,以符合咱們特定的須要,沒法作到在一個函數中既使用又釋放。這個例子也說明了內存泄露最容易發生的地方:即兩個部分的接口部分,一個函數申請內存,一個函數釋放內存。而且這些函數由不一樣的人開發、使用,這樣形成內存泄露的可能性就比較大了。這須要養成良好的單元測試習慣,將內存泄露消滅在初始階段。
結果分析:
假設上述文件名位tree.h, tree.cpp, badleak.cpp,生成的可執行程序爲badleak,用memcheck對其進行測試,輸出以下。
該示例程序是生成一棵樹的過程,每一個樹節點的大小爲12(考慮內存對齊),共8個節點。從上述輸出能夠看出,全部的內存泄露都被發現。Memcheck將內存泄露分爲兩種,一種是可能的內存泄露(Possibly lost),另一種是肯定的內存泄露(Definitely lost)。Possibly lost 是指仍然存在某個指針可以訪問某塊內存,但該指針指向的已經不是該內存首地址。Definitely lost 是指已經不可以訪問這塊內存。而Definitely lost又分爲兩種:直接的(direct)和間接的(indirect)。直接和間接的區別就是,直接是沒有任何指針指向該內存,間接是指指向該內存的指針都位於內存泄露處。在上述的例子中,根節點是directly lost,而其餘節點是indirectly lost。
本文介紹了valgrind的體系結構,並重點介紹了其應用最普遍的工具:memcheck。闡述了memcheck發現內存問題的基本原理,基本使用方法,以及利用memcheck如何發現目前開發中最普遍的五大類內存問題。在項目中儘早的發現內存問題,可以極大地提升開發效率,valgrind就是可以幫助你實現這一目標的出色工具。