如何使用Valgrind memcheck工具進行C/C++的內存泄漏檢測

jopen 11年前發布 | 18K 次閱讀 C/C++開發 C/C++

系統編程中一個重要的方面就是有效地處理與內存相關的問題。你的工作越接近系統,你就需要面對越多的內存問題。有時這些問題非常瑣碎,而更多時候它會演變成一個調試內存問題的惡夢。所以,在實踐中會用到很多工具來調試內存問題。

在本文中,我們將討論最流行的開源內存管理框架 VALGRIND。

摘自 Valgrind.org:

Valgrind是用于構建動態分析工具的探測框架。它包括一個工具集,每個工具執行某種類型的調試、分析或類似的任務,以幫助完善你的程序。Valgrind的架構是模塊化的,所以可以容易地創建新的工具而又不會擾亂現有的結構。

</blockquote>

許多有用的工具被作為標準而提供。

  1. Memcheck是一個內存錯誤檢測器。它有助于使你的程序,尤其是那些用C和C++寫的程序,更加準確。
  2. Cachegrind是一個緩存和分支預測分析器。它有助于使你的程序運行更快。
  3. Callgrind是一個調用圖緩存生成分析器。它與Cachegrind的功能有重疊,但也收集Cachegrind不收集的一些信息。
  4. Helgrind是一個線程錯誤檢測器。它有助于使你的多線程程序更加準確。
  5. DRD也是一個線程錯誤檢測器。它和Helgrind相似,但使用不同的分析技術,所以可能找到不同的問題。
  6. Massif是一個堆分析器。它有助于使你的程序使用更少的內存。
  7. DHAT是另一種不同的堆分析器。它有助于理解塊的生命期、塊的使用和布局的低效等問題。
  8. SGcheck是一個實驗工具,用來檢測堆和全局數組的溢出。它的功能和Memcheck互補:SGcheck找到Memcheck無法找到的問題,反之亦然。
  9. BBV是個實驗性質的SimPoint基本塊矢量生成器。它對于進行計算機架構的研究和開發很有用處。
  10. </ol>

    也有一些對大多數用戶沒有用的小工具:Lackey是演示儀器基礎的示例工具;Nulgrind是一個最小化的Valgrind工具,不做分析或者操作,僅用于測試目的。

    在這篇文章我們將關注“memcheck”工具。

    使用 Valgrind Memcheck

    memcheck工具的使用方式如下:

    valgrind --tool=memcheck ./a.out

    從上面的命令可以清楚的看到, 主要的命令是valgrind,而我們想使用的工具是通過'-tool'選項來指定的. 上面的‘a.out’指的是我們想使用memcheck運行的可執行文件.

    該工具可以檢測下列與內存相關的問題 :

    • 未釋放內存的使用
    • 對釋放后內存的讀/寫
    • 對已分配內存塊尾部的讀/寫
    • 內存泄露
    • 不匹配的使用malloc/new/new[] 和 free/delete/delete[]
    • 重復釋放內存
    • </ul>

      注意: 上面列出的并不很全面,但卻包含了能被該工具檢測到的很多普遍的問題.

      讓我們一個一個地對上面的場景進行討論:

      注意: 下面討論的所有測試代碼都應該使用gcc并且加上-g選項(用來在memcheck的輸出中生成行號)進行編譯. 就想我們之前討論過的 C程序被編譯成可執行文件, 它需要經歷四個不同的階段.

      1. 使用未初始化的內存

      Code :

      #include <stdio.h>

      include <stdlib.h>

      int main(void) { char *p;

      char c = *p; 
      
      printf("\n [%c]\n",c); 
      
      return 0;
      

      }</pre>

      在上面的代碼中,我們嘗試使用未初始化的指針 ‘p’.

      讓我們運行Memcheck來看下結果.

      $ valgrind --tool=memcheck ./val
      ==2862== Memcheck, a memory error detector
      ==2862== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
      ==2862== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
      ==2862== Command: ./val
      ==2862==
      ==2862== Use of uninitialised value of size 8
      ==2862==    at 0x400530: main (valgrind.c:8)
      ==2862==

      [#] ==2862== ==2862== HEAP SUMMARY: ==2862== in use at exit: 0 bytes in 0 blocks ==2862== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==2862== ==2862== All heap blocks were freed -- no leaks are possible ==2862== ==2862== For counts of detected and suppressed errors, rerun with: -v ==2862== Use --track-origins=yes to see where uninitialized values come from ==2862== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)</pre>

      從上面的輸出可以看到,Valgrind檢測到了未初始化的變量,然后給出了警告(上面加粗的幾行(譯者注:貌似上面沒有加粗的)).

      2. 在內存被釋放后進行讀/寫

      Code :

      #include <stdio.h>

      include <stdlib.h>

      int main(void) { char p = malloc(1); p = 'a';

      char c = *p; 
      
      printf("\n [%c]\n",c); 
      
      free(p);
      c = *p;
      return 0;
      

      }</pre>

      上面的代碼中,我們有一個釋放了內存的指針 ‘p’ 然后我們又嘗試利用指針獲取值.

      讓我們運行memcheck來看一下Valgrind對這種情況是如何反應的.

      $ valgrind --tool=memcheck ./val
      ==2849== Memcheck, a memory error detector
      ==2849== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
      ==2849== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
      ==2849== Command: ./val
      ==2849==

      [a] ==2849== Invalid read of size 1 ==2849== at 0x400603: main (valgrind.c:30) ==2849== Address 0x51b0040 is 0 bytes inside a block of size 1 free'd ==2849== at 0x4C270BD: free (vg_replace_malloc.c:366) ==2849== by 0x4005FE: main (valgrind.c:29) ==2849== ==2849== ==2849== HEAP SUMMARY: ==2849== in use at exit: 0 bytes in 0 blocks ==2849== total heap usage: 1 allocs, 1 frees, 1 bytes allocated ==2849== ==2849== All heap blocks were freed -- no leaks are possible ==2849== ==2849== For counts of detected and suppressed errors, rerun with: -v ==2849== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)</pre>

      從上面的輸出內容可以看到,Valgrind檢測到了無效的讀取操作然后輸出了警告 ‘Invalid read of size 1′.

      另注,使用gdb來調試c程序.

      3. 從已分配內存塊的尾部進行讀/寫

      Code :

      #include <stdio.h>

      include <stdlib.h>

      int main(void) { char p = malloc(1); p = 'a';

      char c = *(p+1); 
      
      printf("\n [%c]\n",c); 
      
      free(p);
      return 0;
      

      }</pre>

      在上面的代碼中,我們已經為‘p’分配了一個字節的內存,但我們在將值讀取到 ‘c’中的時候使用的是地址p+1.

      現在我們使用Valgrind運行上面的代碼 :

      $ valgrind --tool=memcheck ./val
      ==2835== Memcheck, a memory error detector
      ==2835== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
      ==2835== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
      ==2835== Command: ./val
      ==2835==
      ==2835== Invalid read of size 1
      ==2835==    at 0x4005D9: main (valgrind.c:25)
      ==2835==  Address 0x51b0041 is 0 bytes after a block of size 1 alloc'd
      ==2835==    at 0x4C274A8: malloc (vg_replace_malloc.c:236)
      ==2835==    by 0x4005C5: main (valgrind.c:22)
      ==2835==

      [] ==2835== ==2835== HEAP SUMMARY: ==2835== in use at exit: 0 bytes in 0 blocks ==2835== total heap usage: 1 allocs, 1 frees, 1 bytes allocated ==2835== ==2835== All heap blocks were freed -- no leaks are possible ==2835== ==2835== For counts of detected and suppressed errors, rerun with: -v ==2835== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)</pre>

      同樣,該工具在這種情況下也檢測到了無效的讀取操作.

      4. 內存泄露

      Code:

      #include <stdio.h>

      include <stdlib.h>

      int main(void) { char p = malloc(1); p = 'a';

      char c = *p; 
      
      printf("\n [%c]\n",c); 
      
      return 0;
      

      }</pre>

      在這次的代碼中, 我們申請了一個字節但是沒有將它釋放.現在讓我們運行Valgrind看看會發生什么:

      $ valgrind --tool=memcheck --leak-check=full ./val
      ==2888== Memcheck, a memory error detector
      ==2888== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
      ==2888== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
      ==2888== Command: ./val
      ==2888==

      [a] ==2888== ==2888== HEAP SUMMARY: ==2888== in use at exit: 1 bytes in 1 blocks ==2888== total heap usage: 1 allocs, 0 frees, 1 bytes allocated ==2888== ==2888== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==2888== at 0x4C274A8: malloc (vg_replace_malloc.c:236) ==2888== by 0x400575: main (valgrind.c:6) ==2888== ==2888== LEAK SUMMARY: ==2888== definitely lost: 1 bytes in 1 blocks ==2888== indirectly lost: 0 bytes in 0 blocks ==2888== possibly lost: 0 bytes in 0 blocks ==2888== still reachable: 0 bytes in 0 blocks ==2888== suppressed: 0 bytes in 0 blocks ==2888== ==2888== For counts of detected and suppressed errors, rerun with: -v ==2888== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)</pre>

      輸出行(上面加粗的部分)顯示,該工具能夠檢測到內存的泄露.

      注意: 在這里我們增加了一個選項‘–leak-check=full’來得到內存泄露的詳細細節.

      5. 不匹配地使用malloc/new/new[] 和 free/delete/delete[]

      Code:

      #include <stdio.h>

      include <stdlib.h>

      include<iostream>

      int main(void) { char p = (char)malloc(1); *p = 'a';

      char c = *p; 
      
      printf("\n [%c]\n",c);
      delete p;
      return 0;
      

      }</pre>

      上面的代碼中,我們使用了malloc()來分配內存,但是使用了delete操作符來刪除內存.

      注意 : 使用g++來編譯上面的代碼,因為delete操作符是在C++中引進的,而要編譯C++需要使用g++.

      讓我們運行來看一下 :

      $ valgrind --tool=memcheck --leak-check=full ./val
      ==2972== Memcheck, a memory error detector
      ==2972== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
      ==2972== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
      ==2972== Command: ./val
      ==2972==

      [a] ==2972== Mismatched free() / delete / delete [] ==2972== at 0x4C26DCF: operator delete(void*) (vg_replace_malloc.c:387) ==2972== by 0x40080B: main (valgrind.c:13) ==2972== Address 0x595e040 is 0 bytes inside a block of size 1 alloc'd ==2972== at 0x4C274A8: malloc (vg_replace_malloc.c:236) ==2972== by 0x4007D5: main (valgrind.c:7) ==2972== ==2972== ==2972== HEAP SUMMARY: ==2972== in use at exit: 0 bytes in 0 blocks ==2972== total heap usage: 1 allocs, 1 frees, 1 bytes allocated ==2972== ==2972== All heap blocks were freed -- no leaks are possible ==2972== ==2972== For counts of detected and suppressed errors, rerun with: -v ==2972== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)</pre>

      從上面的輸出可以看到 (加粗的行), Valgrind清楚的說明了‘不匹配的使用了free() / delete / delete []‘

      你可以嘗試在測試代碼中使用'new'和'free'進行組合來看看Valgrind給出的結果是什么.

      6. 兩次釋放內存

      Code :

      #include <stdio.h>

      include <stdlib.h>

      int main(void) { char p = (char)malloc(1); *p = 'a';

      char c = *p;
      printf("\n [%c]\n",c);
      free(p);
      free(p);
      return 0;
      

      }</pre>

      在上面的代碼中, 我們兩次釋放了'p'指向的內存. 現在讓我們運行memcheck :

      $ valgrind --tool=memcheck --leak-check=full ./val
      ==3167== Memcheck, a memory error detector
      ==3167== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
      ==3167== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
      ==3167== Command: ./val
      ==3167==

      [a] ==3167== Invalid free() / delete / delete[] ==3167== at 0x4C270BD: free (vg_replace_malloc.c:366) ==3167== by 0x40060A: main (valgrind.c:12) ==3167== Address 0x51b0040 is 0 bytes inside a block of size 1 free'd ==3167== at 0x4C270BD: free (vg_replace_malloc.c:366) ==3167== by 0x4005FE: main (valgrind.c:11) ==3167== ==3167== ==3167== HEAP SUMMARY: ==3167== in use at exit: 0 bytes in 0 blocks ==3167== total heap usage: 1 allocs, 2 frees, 1 bytes allocated ==3167== ==3167== All heap blocks were freed -- no leaks are possible ==3167== ==3167== For counts of detected and suppressed errors, rerun with: -v ==3167== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)</pre>

      從上面的輸出可以看到(加粗的行), 該功能檢測到我們對同一個指針調用了兩次釋放內存操作.

      在本文中,我們把注意力放在了內存管理框架Valgrind,然后使用memcheck(Valgrind框架提供的)工具來了解它是如何降低需要經常操作內存的程序員的負擔的. 該工具能夠檢測到很多手動檢測不到的與內存相關的問題

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!