概述#

透過特定的編譯與連結選項,可以讓程式碼和函式庫在執行時進行更嚴格的檢查,自動偵測記憶體相關的錯誤。這些檢查主要適用於 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 機制來偵測記憶體洩漏:

  1. 在程式開頭呼叫 mtrace()
  2. 設定環境變數 MALLOC_TRACE 指定追蹤輸出檔
  3. 對輸出檔執行 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:10

AddressSanitizer (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_PATHASAN_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 記憶體檢查
  • 這些工具都有效能代價,應在開發與測試環境中使用,而非直接部署到生產環境