如何使用Valgrind memcheck工具進行C

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

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

摘自 Valgrind.org:

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

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

  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基本塊矢量生成器。它對於進行計算機架構的研究和開發很有用處。

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

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

使用 Valgrind Memcheck

memcheck工具的使用方式如下:

valgrind --tool=memcheck ./a.out

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

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

  • 未釋放內存的使用
  • 對釋放後內存的讀/寫
  • 對已分配內存塊尾部的讀/寫
  • 內存洩露
  • 不匹配的使用malloc/new/new[] 和 free/delete/delete[]
  • 重複釋放內存

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

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

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

1. 使用未初始化的內存

Code :

#include 
#include
int main(void)

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

在上面的代碼中,我們嘗試使用未初始化的指針 ‘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)

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

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

Code :

#include 
#include
int main(void)
{
char *p = malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
free(p);
c = *p;
return 0;
}

上面的代碼中,我們有一個釋放了內存的指針 ‘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)

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

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

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

Code :

#include 
#include
int main(void)
{
char *p = malloc(1);
*p = 'a';
char c = *(p+1);
printf("\n [%c]\n",c);
free(p);
return 0;
}

在上面的代碼中,我們已經為‘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)

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

4. 內存洩露

Code:

#include 
#include
int main(void)
{
char *p = malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
return 0;
}

在這次的代碼中, 我們申請了一個字節但是沒有將它釋放.現在讓我們運行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)

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

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

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

Code:

#include 
#include
#include
int main(void)
{
char *p = (char*)malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
delete p;
return 0;
}

上面的代碼中,我們使用了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)

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

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

6. 兩次釋放內存

Code :

#include 
#include
int main(void)
{
char *p = (char*)malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
free(p);
free(p);
return 0;
}

在上面的代碼中, 我們兩次釋放了'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)

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

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

小編我看過下面的機器算法專欄還不錯,推薦下!


分享到:


相關文章: