概述#
透過特定的編譯與連結選項,可以讓程式碼和函式庫在執行時進行更嚴格的檢查,自動偵測記憶體相關的錯誤。這些檢查主要適用於 C、C++ 和 Objective-C,因為這些語言通常不做 buffer bounds checking,代價是啟用檢查後程式會明顯變慢。
在即時系統(real-time systems)和效能關鍵環境中,需謹慎評估啟用這些檢查的影響。
C++ STL 除錯模式#
啟用 STL 除錯檢查後,能自動偵測:
- Iterator 越界(遞增超過範圍末端)
- 解參照已失效的 iterator(容器被銷毀後)
- 違反演算法前置條件(如未排序的輸入傳給需要排序的演算法)
啟用方式#
| 環境 | 方法 |
|---|---|
| GCC (GNU) | 編譯時定義 _GLIBCXX_DEBUG 巨集 |
| Visual Studio | 使用 Debug 模式建置,或傳遞 /MDd 選項 |
範例:越界存取#
#include <vector>
int main()
{
std::vector<int> v;
v[0] = 3; // 存取空 vector 的第 0 個元素
}GCC 會報錯:attempt to subscript container with out-of-bounds index 0, but container only holds 0 elements
範例:未排序輸入#
std::vector<int> s1 = {5, 3, 2}; // 未排序!
std::vector<int> s2 = {1, 3, 2};
std::vector<int> result;
std::set_intersection(s1.begin(), s1.end(),
s2.begin(), s2.end(),
std::back_inserter(result));GCC 會報錯:elements in iterator range [__first1, __last1) are not sorted
進一步定義
_GLIBCXX_DEBUG_PEDANTIC,還能收到關於使用非可攜 STL 功能的警告。
GNU C 記憶體洩漏檢測:mtrace#
GNU C 函式庫提供 mtrace 機制來偵測記憶體洩漏:
- 在程式開頭呼叫
mtrace() - 設定環境變數
MALLOC_TRACE指定追蹤輸出檔 - 對輸出檔執行
mtrace命令
#include <stdlib.h>
#include <mcheck.h>
int main()
{
#ifndef NDEBUG
mtrace();
#endif
char *c = malloc(42);
return 0;
}輸出會精確指出洩漏的記憶體在哪裡被配置:
Memory not freed:
-----------------
Address Size Caller
0x090e5378 0x2a at leak.c:10AddressSanitizer (ASan)#
AddressSanitizer 是更全面(但也更昂貴)的記憶體錯誤偵測工具,由 GCC 和 LLVM Clang 支援:
啟用方式#
gcc -fsanitize=address -g -fno-omit-frame-pointer myprogram.c能偵測的問題#
- Buffer overflow(stack、heap、global)
- Use-after-free
- 記憶體洩漏
- 各種記憶體存取錯誤
範例:全域緩衝區溢位#
int main()
{
int i, a[5];
for (i = 0; i < sizeof(a); i++) // 錯誤:應為 sizeof(a)/sizeof(a[0])
a[i] = i;
}ASan 會產生詳細的錯誤報告,包含溢位位址、變數定義位置等資訊。
特性與限制#
- 支援 GNU/Linux、OS X、FreeBSD(i386 與 x86_64)、Android on ARM、iOS Simulator
- 記憶體與處理開銷約為 2 倍
- 不預期產生 false positives
- 設定
ASAN_SYMBOLIZER_PATH和ASAN_OPTIONS=symbolize=1可獲得原始碼層級的錯誤報告
Visual Studio 的 CRT 除錯功能#
Visual Studio 的 C debug library 提供記憶體檢查功能,但不如 ASan 全面:
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
int main()
{
// 輸出導向 stderr
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
// 程式結束時偵測記憶體洩漏
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
{
char *c = malloc(42);
c[42] = 'a'; // 越界寫入
}
// 檢查所有記憶體區塊的溢位
_CrtCheckMemory();
return 0;
}CRT 除錯功能只能偵測 heap 區塊邊界外的寫入。與 ASan 不同,它無法偵測無效的讀取操作,也無法檢查 global 和 stack 記憶體的存取。
Guard Malloc (OS X / iOS)#
Guard Malloc (libgmalloc) 是 OS X 和 iOS 開發的替代方案:
- 將每個記憶體配置放在獨立的虛擬記憶體頁面中
- 能偵測超出配置頁面的記憶體存取
- 不需要額外的 CPU 開銷檢查記憶體存取,但會大量消耗虛擬記憶體
- 支援 C、C++、Objective-C
重點回顧#
- 啟用 C++ STL 除錯模式可自動偵測 iterator 錯誤與演算法前置條件違規
- mtrace 是輕量級的 GNU C 記憶體洩漏偵測方案
- AddressSanitizer 是最全面的記憶體錯誤偵測工具,建議在測試階段常態啟用
- Visual Studio 的 CRT debug library 提供基本的 heap 記憶體檢查
- 這些工具都有效能代價,應在開發與測試環境中使用,而非直接部署到生產環境