C 語言無內置內存保護,越界訪問、內存洩漏是頭號 Bug 來源。本文提供兩套生產級輕量解決方案:**邊界標記法(紅區/護衛頁思想)檢測越界**、**全局分配計數法監測洩漏**,附完整頭文件、實現代碼、測試用例與原理圖解。
分配內存時,在用戶區**前後各放置已知模式的護衛區**(如 `0xDEADBEEF` 或全 0)。操作前後檢查護衛區是否被修改,若修改則發生越界。
內存佈局:
[Header: size] [Guard: 4 bytes] [User Data: size bytes] [Guard: 4 bytes]
↑ ↑ ↑
系統返回指針 用戶指針(p) 尾部護衛區
#ifndef CHECKMEM_H
#define CHECKMEM_H
#include
void* mc_malloc(size_t sz); // 分配+雙護衛區
void* mc_calloc(size_t n, size_t sz);
void* mc_realloc(void* ptr, size_t sz);
void mc_check(void* ptr); // 檢查護衛區,越界直接報錯退出
void mc_free(void* ptr); // 檢查後釋放
#endif
#define GUARD_SIZE 4
#define GUARD_PATTERN 0xAA // 易識別模式
void* mc_malloc(size_t sz) {
char* raw = calloc(1, sz + 2*GUARD_SIZE + sizeof(size_t));
if (!raw) { perror("mc_malloc"); exit(1); }
// 頭部存儲用戶區大小
*(size_t*)raw = sz;
// 前護衛區
memset(raw + sizeof(size_t), GUARD_PATTERN, GUARD_SIZE);
// 用戶指針
void* user_ptr = raw + sizeof(size_t) + GUARD_SIZE;
// 尾護衛區已在 calloc 中清零,或可填充另一模式
return user_ptr;
}
void mc_check(void* ptr) {
char* raw = (char*)ptr - sizeof(size_t) - GUARD_SIZE;
size_t sz = *(size_t*)raw;
// 檢查前護衛區
for (int i = 0; i < GUARD_SIZE; i++)
if (raw[sizeof(size_t)+i] != GUARD_PATTERN) { fprintf(stderr, "Front guard corrupted at %p!\n", raw+sizeof(size_t)+i); exit(1); }
// 檢查後護衛區
char* tail_guard = raw + sizeof(size_t) + GUARD_SIZE + sz;
for (int i = 0; i < GUARD_SIZE; i++)
if (tail_guard[i] != 0) { fprintf(stderr, "Tail guard corrupted at %p!\n", tail_guard+i); exit(1); }
}
int main() {
char* p = mc_malloc(16);
mc_check(p); // 通過
// p[16] = 1; // 故意越界寫入尾護衛區
// mc_check(p); // 觸發報錯:Tail guard corrupted!
p = mc_realloc(p, 32);
mc_check(p);
mc_free(p);
return 0;
}
宏替換 `malloc/free` 為帶計數器版本,程序退出時(`atexit`)檢查計數器歸零,否則報告洩漏並記錄日誌。
#ifndef MEMGLOBAL_H
#define MEMGLOBAL_H
#include
void mg_start(void); // 啟動監測(註冊退出鉤子)
void* mg_malloc(size_t sz);
void* mg_calloc(size_t n, size_t sz);
void* mg_realloc(void* ptr, size_t sz);
void mg_free(void* ptr);
// Debug 模式自動替換標準函數
#ifdef _DEBUG
#define malloc mg_malloc
#define free mg_free
#define calloc mg_calloc
#define realloc mg_realloc
#endif
#endif
static int g_alloc_count = 0;
static const char* LOG_FILE = "mem_leak.log";
void mg_start() { atexit(mg_exit_check); }
void* mg_malloc(size_t sz) {
void* p = malloc(sz);
if (p) { g_alloc_count++; memset(p, 0, sz); }
return p;
}
void mg_free(void* p) {
if (!p) return;
g_alloc_count--;
free(p);
}
void mg_exit_check() {
if (g_alloc_count == 0) return;
fprintf(stderr, "MEMORY LEAK DETECTED: %d blocks not freed!\n", g_alloc_count);
FILE* f = fopen(LOG_FILE, "a");
if (f) { fprintf(f, "LEAK: %d blocks at exit\n", g_alloc_count); fclose(f); }
}
#include "memglobal.h"
int main() {
mg_start();
int* p = malloc(100); // 計數+1
p = realloc(p, 200); // 計數不變
free(p); // 計數-1
// 故意洩漏:malloc(50) 不 free
malloc(50);
return 0; // 退出觸發 atexit 報告洩漏
}
輸出:`MEMORY LEAK DETECTED: 1 blocks not freed!` 並寫入 `mem_leak.log`。
| 維度 | 邊界標記法 | 全局計數法 |
|---|---|---|
| 檢測目標 | 緩衝區越界(寫越界/讀越界) | 內存洩漏(未釋放) |
| 檢測時機 | 調用 `mc_check()` 時(可插桩在關鍵點) | 程序退出時(自動) |
| 內存開銷 | 每次分配 +12 字節(頭+雙護衛) | 僅一個全局計數器 |
| 性能影響 | 微小(檢查時遍歷護衛區) | 極低(原子增減) |
| 適用階段 | 開發/測試階段,關鍵模塊 | 全生命周期,CI/CD 集成 |
| 線程安全 | 需鎖保護護衛區檢查 | 需原子操作計數器 |
**「邊界標記法」守護每次分配的邊界,「全局計數法」守護整個程序的平衡**。兩套輕量代碼(各 ~100 行)即可構建 C 項目內存安全防線。開發階段建議:Debug 構建啟用全局計數 + 關鍵模塊啟用邊界標記;發布前跑一遍 ASan/Valgrind 全量掃描。內存安全,從「碰運氣」變成「有保障」。