【Valgrind教學】8分鐘學會使用Valgrind偵測C與C++的Memory Leaks

Memory leak源自於不良的程式設計,它是指在一個程序申請了一塊記憶體(通常是使用如malloc()new等函數)但未能適當地釋放(free or delete)。這會導致即使這一塊記憶體已經不再被該程式使用,這塊記憶體也一直無法被作業系統或其他程序回收和使用。這樣程式設計上的問題會導致以下問題:

  1. 資源浪費: 洩漏的記憶體占用系統資源,導致系統可用記憶體減少。
  2. 性能下降: 隨著時間的推移,洩漏的記憶體會積累,可能會導致系統變慢或崩潰。
  3. 不可預測的行為: 程序可能會由於記憶體不足而發生不可預測的行為。

Memory leak在大型或複雜的程式中很難手動找到。Valgrind等工具能自動檢測C和C++程序中的memory leak和其他記憶體相關的問題。在本文中,我們將學習如何使用Valgrind來檢測和修復C和C++程式中的記憶體錯誤。

Valgrind

Valgrind簡介

Valgrind是一個open-source tool,專門用於對C和C++程式進行debug、效能分析和記憶體分析。它提供了完整的功能,可以檢測到各種記憶體錯誤,包括memory leak、buffer overflow和heap corruptions等。此外,Valgrind也能提供關於記憶體使用和效能瓶頸的詳細信息。

安裝Valgrind

Valgrind可在Linux和macOS系統上執行。在Ubuntu上,你可以使用以下命令安裝Valgrind:

sudo apt-get install valgrind

macOS上,可以使用Homebrew進行安裝:

brew install valgrind

程式編譯

在用Valgrind執行程序之前,你需要用啟用除錯符號(debugging symbols)的方式來編譯它。這些符號會給Valgrind提供更多關於程序碼和數據結構的信息,從而讓它能提供更詳細的記憶體錯誤信息。

要啟用除錯符號來編譯程序,請在編譯指令中加上-g標誌。例如,要編譯一個名為myprog.c的C程序,使用以下命令:

gcc -g myprog.c -o myprog

使用Valgrind執行程序

要使用Valgrind執行程序,使用valgrind指令後面接的程序名。例如,要用Valgrind運行myprog,使用以下命令:

valgrind ./myprog

Valgrind會啟動程式並監視其記憶體使用情況。如果程序有任何記憶體錯誤,Valgrind會檢測到並提供關於這些錯誤的詳細資訊。

分析Valgrind輸出

Valgrind的輸出一開始看起來可能有些難以理解,但它提供了大量有關程式記憶體使用情況的資訊。這些輸出被劃分為多個部分,每個部分提供不同類型的資訊。

最重要的輸出部分是摘要(Summary),它提供了記憶體使用情況的摘要。摘要會顯示已分配和已釋放的Byte數,以及檢測到的memory leak和錯誤的數量。

Valgrind還提供了記憶體錯誤的詳細列表,包括錯誤發生的程式碼位置和涉及的記憶體大小。這些錯誤的log可能會較難第一時間直覺了解內容,但它們在識別和修復記憶體錯誤方面非常有用。

為了更容易分析Valgrind的輸出,你可以使用--leak-check=full選項以啟用詳細的記憶體洩漏報告。這個選項會提供更詳細的記憶體洩漏報告,包括洩漏發生的代碼位置和洩漏的記憶體塊大小。

Valgrind輸出實例

下面是一個Valgrind輸出的範例,這個程式有memory leak:

==1204== HEAP SUMMARY:
==1204==     in use at exit: 4 bytes in 1 blocks
==1204==   total heap usage: 2 allocs, 1 frees, 1,028 bytes allocated
==1204== 
==1204== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1204==    at 0x4C2CE93: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1204==    by 0x40056C: main (leak.c:5)
==1204== 
==1204== LEAK SUMMARY:
==1204==    definitely lost: 4 bytes in 1 blocks
==1204==    indirectly lost: 0 bytes in 0 blocks
==1204==      possibly lost: 0 bytes in 0 blocks
==1204==    still reachable: 0 bytes in 0 blocks
==1204==         suppressed: 0 bytes in 0 blocks
==1204== 
==1204== For counts of detected and suppressed errors, rerun with: -v
==1204== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

在這個範例中,該程式有一個4位元組(bytes)的memory leak,這塊記憶體是在程式的第5行(main (leak.c:5))使用malloc()函數分配的。程式沒有釋放這塊記憶體,導致它在該function結束時依然佔據記憶體。

Valgrind也提供了memory heap使用的總結,包括分配和釋放的總位元組數,以及檢測到的memory leak漏數量。在這個案例中,該程式分配了1,028位元組的記憶體。

在“LEAK SUMMARY”部分,Valgrind提供了它檢測到的memory leak的總結,包括失去的位元組數和涉及的區塊數量。在這個案例中,有一個涉及1個區塊、4位元組的memory leak。

修復記憶體錯誤


一旦你使用Valgrind識別了記憶體錯誤,下一步就是修復它們。最常見的記憶體錯誤是memory leak,這發生在記憶體分配了但未被釋放的情況下,以及buffer overflow,這發生在程式寫入超過已分配記憶體區塊的末端的情況。

為了修復memory leak,你需要確保在程式退出之前釋放所有分配的記憶體。這可以透過追踪所有記憶體分配並確保它們與相應的free()相匹配來完成。如果程式過於複雜以至於無法手動追踪所有的分配和釋放,你可以使用如smart pointer、garbage collection或memory management libraries等工具來自動化記憶體管理。

為了修buffer overflow,你需要確保所有記憶體的存取都在分配的記憶體區塊的範圍內。這可以透過使用如邊界檢查(bounds checking)或範圍檢查(range checking)來完成。

進階Valgrind使用

Valgrind提供了幾個進階功能,可用於分析和最佳化記憶體使用和性能。

其中一個功能是生成call graphs,這可以用於看程式的函數調用和它們之間的關係。要生成call graphs,使用--callgrind-out-file選項,後跟一個文件名,然後在Valgrind下執行程式。Valgrind將生成一個可以使用如KCachegrind或QCacheGrind等工具查看的call graphs。

Valgrind的另一個特性是能夠模擬不同的記憶體分配策略,如first-fit、best-fit和worst-fit。要模擬不同的分配策略,使用--malloc-fill--free-fill選項。Valgrind將會使用指定的策略模擬記憶體分配,並提供有關記憶體使用和碎片化 (fragmentation) 的詳細資訊。

使用Valgrind監控子程式的記憶體使用

Valgrind是一個強大的工具,用於檢測C和C++程式中的記憶體錯誤。它也可以用來監控子程式的記憶體使用。這在你有一個通過exec系統調用啟動子程式的父程式,並且你想監控所有子程式的記憶體使用時特別有用。在這裡,我們將探討如何使用Valgrind中的--trace-children選項來監控子程式的記憶體使用。

什麼是–trace-children?

Valgrind中的--trace-children選項告訴Valgrind跟踪子程式並監控其記憶體使用。這個選項在你有一個通過exec系統調用啟動多個子程式的父程式,並且你想監控所有子程式的記憶體使用時非常有用。

如何使用–trace-children?

要使用--trace-children選項,只需將其添加到運行程式時的Valgrind命令行中。例如,假設你有一個名為parent的程式,它生成了一個名為child的子程式。你可以這樣使用--trace-children選項在Valgrind下運行parent程式:

valgrind --trace-children=yes ./parent

結論

Valgrind是一個強大的工具,可以用來檢測和修復C和C++程式中的記憶體錯誤。透過本文概述的步驟,你可以使用Valgrind識別記憶體錯誤、分析記憶體使用和提升程式效能。使用啟用了debugging symbols的編譯選項編譯你的程式,然後在Valgrind下運行它,並仔細分析輸出以確保你的程式沒有記憶體錯誤。

X. Ryan
X. Ryan

Hello!我是一個在矽谷工作,有軟體工程背景的量子計算科學家。這裡分享的內容主要是把平常研究開發時所用的小工具以及看過的東西記錄下來,同時也分享一些日常生活瑣事。

文章: 45