萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 作業系統 >  >> Linux 教學

Valgrind 記憶體檢測實戰:7 大經典錯誤類型、Memcheck 輸出解讀與抑制規則

Valgrind Memcheck 是 C/C++ 記憶體錯誤檢測黃金標準。本文構造涵蓋 7 類經典錯誤的測試代碼,詳細解讀 Memcheck 輸出報告結構,並介紹抑制檔案與 CI 整合最佳實踐。

測試代碼:7 類經典錯誤


#include 
#include 
#include 

static void mem_leak1(void) {           // 1. 動態記憶體洩漏
    char *p = malloc(1);
}

static void mem_leak2(void) {           // 2. 資源洩漏 (FILE*)
    FILE *fp = fopen("test.txt", "w");
}

static void mem_overrun1(void) {        // 3. 動態記憶體越界寫
    char *p = malloc(1);
    *(short*)p = 2;                     // 寫入 2 bytes 到 1 byte 分配區
    free(p);
}

static void mem_overrun2(void) {        // 4. 陣列/棧越界
    char array[5];
    strcpy(array, "hello");             // 寫入 6 bytes (含 \0) 到 5 byte 陣列
}

static void mem_double_free(void) {     // 5. 重複釋放
    char *p = malloc(1);
    free(p);
    free(p);
}

static void mem_use_wild_pointer(void) { // 6. 使用野指針 (未初始化)
    char *p = (void*)0x80184800;
    *p = 1;
}

static void mem_free_wild_pointer(void) { // 7. 釋放野指針
    char *p;                            // 未初始化
    free(p);
}

int main() {
    mem_leak1();
    mem_leak2();
    mem_overrun1();
    mem_overrun2();
    mem_double_free();
    // mem_use_wild_pointer();  // 會直接 Crash,Valgrind 無法捕獲
    mem_free_wild_pointer();
    return 0;
}

編譯與執行檢測


gcc -g -O0 -o mem_test mem_test.c
valgrind --track-fds=yes --leak-check=full --undef-value-errors=yes ./mem_test
  • -g -O0:保留除錯資訊、關閉優化 (關鍵!)
  • --track-fds=yes:追蹤檔案描述符洩漏
  • --leak-check=full:詳細洩漏報告
  • --undef-value-errors=yes:檢測未初始化值使用

報告輸出深度解讀

錯誤 1:Invalid Write (越界寫入)


==1234== Invalid write of size 2
==1234==    at 0x4005B4: mem_overrun1 (mem_test.c:18)
==1234==    by 0x400653: main (mem_test.c:42)
==1234==  Address 0x5203040 is 0 bytes inside a block of size 1 alloc'd
==1234==    at 0x4C2DB8F: malloc (vg_replace_malloc.c:299)
==1234==    by 0x4005AD: mem_overrun1 (mem_test.c:17)
  • Invalid write of size 2:寫入 2 字節
  • 0 bytes inside a block of size 1:偏移 0 但區塊僅 1 字節 → 越界
  • 堆疊回溯定位到 mem_overrun1 第 18 行 *(short*)p = 2

錯誤 2:Invalid Free (重複釋放)


==1234== Invalid free() / delete / delete[]
==1234==    at 0x4C2E7B0: free (vg_replace_malloc.c:480)
==1234==    by 0x400614: mem_double_free (mem_test.c:30)
==1234==  Address 0x5203080 is 0 bytes inside a block of size 1 free'd
  • 同一地址被 free 兩次
  • 第一次釋放後記錄為 free'd,第二次觸發報錯

錯誤 3:Conditional jump on uninitialised value (野指針釋放)


==1234== Conditional jump or move depends on uninitialised value(s)
==1234==    at 0x4C2E730: free (vg_replace_malloc.c:480)
==1234==    by 0x40063C: mem_free_wild_pointer (mem_test.c:37)
  • free 內部判斷指針有效性時讀取未初始化記憶體
  • 緊接著報 Invalid free (地址碰巧落在已釋放區塊)

錯誤 4:FILE Descriptor Leak (資源洩漏)


==1234== FILE DESCRIPTORS: 4 open at exit.
==1234== Open file descriptor 3: test.txt
==1234==    at 0x4E8D13: __open_nocancel (open.c:??)
==1234==    by 0x4D58EC: __fopen_internal (iofopen.c:??)
==1234==    by 0x400596: mem_leak2 (mem_test.c:12)
  • 描述符 0/1/2 為 stdin/stdout/stderr 可忽略
  • 描述符 3 為 fopen 開啟 test.txt 未 fclose

Heap Summary 與 Leak Summary


==1234== HEAP SUMMARY:
==1234==     in use at exit: 353 bytes in 2 blocks
==1234==     total heap usage: 4 allocs, 4 frees, 355 bytes allocated

==1234== LEAK SUMMARY:
==1234==    definitely lost: 1 bytes in 1 blocks (mem_leak1)
==1234==    indirectly lost: 0 bytes in 0 blocks
==1234==    possibly lost: 0 bytes in 0 blocks
==1234==    still reachable: 352 bytes in 1 blocks (libc 內部緩存等)
  • definitely lost:確定洩漏 (無指針引用)
  • indirectly lost:因引用塊洩漏而連帶洩漏
  • still reachable:程式結束時仍有指針指向,非嚴格洩漏但建議清理

進階:抑制已知誤報


# 生成抑制檔
valgrind --gen-suppressions=all ./mem_test 2> valgrind.supp

# 編輯 valgrind.supp 保留需抑制的項 (如第三方庫已知問題)
# 使用
valgrind --suppressions=valgrind.supp ./mem_test

CI/CD 整合建議


# GitLab CI .gitlab-ci.yml 片段
valgrind_test:
  script:
    - gcc -g -O0 -o test_bin test.c
    - valgrind --quiet --error-exitcode=1 --leak-check=full --track-fds=yes ./test_bin
  allow_failure: false

Valgrind 報告示例

黃金法則:編譯必加 -g -O0,針對所有新代碼跑 Memcheck,將 --error-exitcode=1 接入 CI 門禁,零容忍記憶體錯誤上線。
copyright © 萬盛學電腦網 all rights reserved