Valgrind Memcheck 是 C/C++ 記憶體錯誤檢測黃金標準。本文構造涵蓋 7 類經典錯誤的測試代碼,詳細解讀 Memcheck 輸出報告結構,並介紹抑制檔案與 CI 整合最佳實踐。
#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:檢測未初始化值使用
==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)
mem_overrun1 第 18 行 *(short*)p = 2
==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,第二次觸發報錯
==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 內部判斷指針有效性時讀取未初始化記憶體
==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)
fopen 開啟 test.txt 未 fclose
==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 內部緩存等)
# 生成抑制檔
valgrind --gen-suppressions=all ./mem_test 2> valgrind.supp
# 編輯 valgrind.supp 保留需抑制的項 (如第三方庫已知問題)
# 使用
valgrind --suppressions=valgrind.supp ./mem_test
# 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

黃金法則:編譯必加 -g -O0,針對所有新代碼跑 Memcheck,將 --error-exitcode=1 接入 CI 門禁,零容忍記憶體錯誤上線。