1. 程式人生 > >記憶體洩露檢測工具Valgrind

記憶體洩露檢測工具Valgrind

記憶體洩露簡介

什麼是記憶體洩漏

  記憶體洩漏(Memory Leak)是指程式中已動態分配的堆記憶體由於某種原因,程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。
  記憶體洩漏缺陷具有隱蔽性、積累性的特徵,比其他記憶體非法訪問錯誤更難檢測。因為記憶體洩漏的產生原因是記憶體塊未被釋放,屬於遺漏型缺陷而不是過錯型缺陷。此外,記憶體洩漏通常不會直接產生可觀察的錯誤症狀,而是逐漸積累,降低系統整體效能,極端的情況下可能使系統崩潰。

記憶體洩露產生的方式

以產生的方式來分類,記憶體洩漏可以分為四類:

  • 常發性會記憶體洩漏:發生記憶體洩漏的程式碼會被多次執行到,每次被執行時都導致一塊記憶體洩漏。
  • 偶發性記憶體洩漏:發生記憶體洩漏的程式碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測記憶體洩漏至關重要。
  • 一次性記憶體洩漏:發生記憶體洩漏的程式碼只會被執行一次,或者由於演算法上的缺陷,導致總會有一塊且僅有一塊記憶體發生洩漏。
  • 隱式記憶體洩漏:程式在執行過程中不停的分配記憶體,但是直到結束的時候才釋放記憶體。嚴格的說這裡並沒有發生記憶體洩漏,因為最終程式釋放了所有申請的記憶體。但是對於一個伺服器程式,需要執行幾天,幾周甚至幾個月,不及時釋放記憶體也可能導致最終耗盡系統的所有記憶體。所以,我們稱這類記憶體洩漏為隱式記憶體洩漏。從使用者使用程式的角度來看,記憶體洩漏本身不會產生什麼危害,作為一般的使用者,根本感覺不到記憶體洩漏的存在。真正有危害的是記憶體洩漏的堆積,這會最終耗盡系統所有的記憶體。從這個角度來說,一次性記憶體洩漏並沒有什麼危害,因為它不會堆積,而隱式記憶體洩漏危害性則非常大,因為較之於常發性和偶發性記憶體洩漏它更難被檢測到。

Valgrind

簡介

  Valgrind具是一個用於除錯和分析Linux程式的GPL系統。使用Valgrind的工套件,您可以自動檢測許多記憶體管理和執行緒錯誤,使程式更穩定。還可以執行詳細的分析以幫助加速程式的執行。
  Valgrind是Julian Seward的作品。Valgrind是執行在Linux上一套基於模擬技術的程式除錯和分析工具,它包含一個核心,一個軟體合成的CPU,和一系列的小工具。如下圖所示:

安裝 

  CentOS安裝:

# sudo yum install valgrind -y

  Ubuntu 安裝:

# sudo apt install valgrind -y

示例程式

  給出一個簡單示例malloc.c 。

#include<stdio.h>
#include<stdlib.h>
void fun()
{
         int *x = malloc(10 * sizeof(int));
         x[10] = 0;
}
int main()
{
         int i = 99;
         fun();
         printf("i = %d\n",i);
         return 0;
}

  以上程式碼存在兩個問題:

  • 沒有free掉申請的資源;
  • fun函式裡面越界了,x[10]是非法的。

Memcheck

  是最常用的小工具,用來檢測程式中出現的記憶體問題,所有對記憶體的讀寫都會被檢測到,一切對malloc和free的呼叫都會被捕獲,它能檢測下列問題:

  • 對未初始化記憶體的使用;
  • 讀/寫釋放後的記憶體塊;
  • 讀/寫超出malloc分配的記憶體塊;
  • 讀/寫不適當的棧中的記憶體塊;
  • 記憶體洩漏,指向一塊記憶體的指標永遠丟失;
  • 不正確的malloc/free或new/delete匹配;
  • memcpy相關函式中的dst和src指標重疊;

  我們使用Memcheck小工具來檢測存在的問題。
  編譯:gcc -Wall -o malloc malloc.c
  檢測:valgrind ./malloc
    --show-reachable=<yes|no> [default: no]
    --leak-check=full 檢視更為詳細資訊

  其中,34496是程式執行時的程序號。
  Invalid write of size 4:表示非法寫入(越界),下面是告訴我們錯誤發生的位置,在main中呼叫的fun函式。
  HEAP SUMMARY:說明了堆的情況,可以看到申請了40個位元組,後面說有1個申請,0個被free。
  LEAK SUMMARY:也是說的堆的洩漏情況,明顯丟失的有40個位元組。
  如果main中的i未初始化,這裡還會有一些其他的錯誤。

# gcc -Wall -o malloc malloc.c                                                                     
malloc.c: In function ‘main’:
malloc.c:22:16: warning: ‘i’ is used uninitialized in this function [-Wuninitialized]   # 未初始化變數
          printf("i = %d\n",i);                                                                                            
                ^
[root@localhost memcheck]# valgrind ./malloc                                                                                
==34555== Memcheck,a memory error detector                                                                                 
==34555== Copyright (C) 2002-2017,and GNU GPL'd,by Julian Seward et al.                                                   
==34555== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info                                                
==34555== Command: ./malloc                                                                                                 
==34555==                                                                                                                   
==34555== Invalid write of size 4                                                                                           
==34555==    at 0x40057B: fun (in /root/memcheck/malloc)                                                                    
==34555==    by 0x400594: main (in /root/memcheck/malloc)                                                                   
==34555==  Address 0x5203068 is 0 bytes after a block of size 40 alloc'd                                                    
==34555==    at 0x4C29BC3: malloc (vg_replace_malloc.c:299)                                                                 
==34555==    by 0x40056E: fun (in /root/memcheck/malloc)
==34555==    by 0x400594: main (in /root/memcheck/malloc)
==34555== 
==34555== Conditional jump or move depends on uninitialised value(s)
==34555==    at 0x4E80B9E: vfprintf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4005A8: main (in /root/memcheck/malloc)
==34555== 
==34555== Use of uninitialised value of size 8
==34555==    at 0x4E7E26B: _itoa_word (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4E824F0: vfprintf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4005A8: main (in /root/memcheck/malloc)
==34555== 
==34555== Conditional jump or move depends on uninitialised value(s)
==34555==    at 0x4E7E275: _itoa_word (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4E824F0: vfprintf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4005A8: main (in /root/memcheck/malloc)
==34555== 
==34555== Conditional jump or move depends on uninitialised value(s)
==34555==    at 0x4E8253F: vfprintf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4005A8: main (in /root/memcheck/malloc)
==34555== 
==34555== Conditional jump or move depends on uninitialised value(s)
==34555==    at 0x4E80C6B: vfprintf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4005A8: main (in /root/memcheck/malloc)
==34555== 
==34555== Conditional jump or move depends on uninitialised value(s)
==34555==    at 0x4E80CEE: vfprintf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)
==34555==    by 0x4005A8: main (in /root/memcheck/malloc)
==34555== 
i = 0
==34555== 
==34555== HEAP SUMMARY:
==34555==     in use at exit: 40 bytes in 1 blocks
==34555==   total heap usage: 1 allocs,0 frees,40 bytes allocated
==34555== 
==34555== LEAK SUMMARY:
==34555==    definitely lost: 40 bytes in 1 blocks
==34555==    indirectly lost: 0 bytes in 0 blocks
==34555==      possibly lost: 0 bytes in 0 blocks
==34555==    still reachable: 0 bytes in 0 blocks
==34555==         suppressed: 0 bytes in 0 blocks
==34555== Rerun with --leak-check=full to see details of leaked memory
==34555== 
==34555== For counts of detected and suppressed errors,rerun with: -v
==34555== Use --track-origins=yes to see where uninitialised values come from
==34555== ERROR SUMMARY: 7 errors from 7 contexts (suppressed: 0 from 0)
View Code

Callgrind

  和gprof 類似的分析工具,但它對程式的執行觀察更細緻入微,能給我們提供更多的資訊。和gprof不同,它不需要在編譯原始碼時新增附加特殊選項,但加上除錯選項是推薦的。
  Callgrind收集程式執行時的一些資料,建立函式呼叫關係圖,還可以有選擇的進行cache模擬。在執行結束時,它會把分析資料寫入一個檔案,callgrind_annotate可以把這個檔案的內容轉化成可讀的形式。

  Callgrind可以幫助我們對程式的執行進行觀察。

# valgrind --tool=callgrind ./malloc

  

  可以看到生成了一個檔案callgrind.out.34755,同樣34755為程式執行時的程序號。當callgrind執行你的程式時,還可以使用callgrind_control來觀察程式的執行,而且不會干擾它的執行。
  顯示程式的詳細資訊:

# callgrind_annotate callgrind.out.34755

  

Cachegrind

  Cache分析器,它模擬CPU中的一級快取I1,DI和二級快取,能夠精確的指出程式中cache的丟失和命中。如果需要,它還能為我們提供cache丟失次數,記憶體引用次數,以及每行程式碼,每個函式,每個模組整個程式產生的指令數,這對優化程式有很大的幫助。  

# valgrind --tool=cachegrind ./mallo

Helgrind

  用來檢測多執行緒程式中出現的競爭問題。Helgrind尋找記憶體中內對個執行緒訪問,而又沒有一貫加鎖的區域。這些區域往往是執行緒之間失去同步的情況,而且會導致難以發掘的錯誤。
  Helgrind實現了名為“Eraser”的競爭檢測演算法,並做了進一步改進,減少了報告錯誤的次數。不過Helgrinf仍然處於實驗階段。

Massif

  堆疊分析器,它能測量程式在堆疊中使用了多少記憶體,告訴我們堆塊,堆管理塊和棧的大小。Massif能幫助我們減少記憶體的使用,在代用虛擬記憶體的現代系統中,它還能加速我們程式的執行,減少程式停留在交換區中的機率。

  此外,lackey和nulgrind也會提供。Lackey是小型工具,很少用到;Nulgrind只是為開發者展示如何建立一個工具。

參考

  Linux下幾款C++程式中的記憶體洩露檢查工具
    https://blog.csdn.net/gatieme/article/details/51959654
  Linux下檢測記憶體洩露的工具 valgrind
    https://cloud.tencent.com/developer/article/1075945
  Valgrind除錯
    http://www.voidcn.com/article/p-xyrqgaum-yq.