Memory leak源自於不良的程式設計,它是指在一個程序申請了一塊記憶體(通常是使用如malloc()
或new
等函數)但未能適當地釋放(free or delete)。這會導致即使這一塊記憶體已經不再被該程式使用,這塊記憶體也一直無法被作業系統或其他程序回收和使用。這樣程式設計上的問題會導致以下問題:
- 資源浪費: 洩漏的記憶體占用系統資源,導致系統可用記憶體減少。
- 性能下降: 隨著時間的推移,洩漏的記憶體會積累,可能會導致系統變慢或崩潰。
- 不可預測的行為: 程序可能會由於記憶體不足而發生不可預測的行為。
Memory leak在大型或複雜的程式中很難手動找到。Valgrind等工具能自動檢測C和C++程序中的memory leak和其他記憶體相關的問題。在本文中,我們將學習如何使用Valgrind來檢測和修復C和C++程式中的記憶體錯誤。

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
- [延伸閱讀] 【軟體工程】使用Google Test提升軟體品質:讓軟體測試覆蓋率提升超過80%
- [延伸閱讀] 【軟體開發】4步驟使用Clang-Tidy提升程式碼品質:從平凡到卓越
- [延伸閱讀] 【軟體開發】3分鐘學會使用 clang-format 自動格式化C++代碼
使用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下運行它,並仔細分析輸出以確保你的程式沒有記憶體錯誤。
- [延伸閱讀] 【NumPy教學】Python數據科學入門:從0到100%精通NumPy函數操作
- [延伸閱讀] 【軟體工程】使用Google Test提升軟體品質:讓軟體測試覆蓋率提升超過80%
- [延伸閱讀] 【軟體開發】4步驟使用Clang-Tidy提升程式碼品質:從平凡到卓越
- [延伸閱讀] 【軟體開發】3分鐘學會使用 clang-format 自動格式化C++代碼
- [延伸閱讀] 【Pytest教學】從0開始:Python 開發者的最佳測試工具