內存泄露檢測工具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.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM