首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言內存越界檢測與洩漏監測:邊界標記法與全局計數法完整實現

     編輯:關於C語言

C 語言內存越界檢測與洩漏監測:邊界標記法與全局計數法完整實現

C 語言無內置內存保護,越界訪問、內存洩漏是頭號 Bug 來源。本文提供兩套生產級輕量解決方案:**邊界標記法(紅區/護衛頁思想)檢測越界**、**全局分配計數法監測洩漏**,附完整頭文件、實現代碼、測試用例與原理圖解。

方案一:邊界標記法檢測越界

核心原理

分配內存時,在用戶區**前後各放置已知模式的護衛區**(如 `0xDEADBEEF` 或全 0)。操作前後檢查護衛區是否被修改,若修改則發生越界。

內存佈局:
[Header: size] [Guard: 4 bytes] [User Data: size bytes] [Guard: 4 bytes]
      ↑                           ↑                      ↑
   系統返回指針              用戶指針(p)           尾部護衛區

核心接口 `checkmem.h`


#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

實現 `checkmem.c` 關鍵片段


#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`)檢查計數器歸零,否則報告洩漏並記錄日誌。

接口 `memglobal.h`


#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

實現 `memglobal.c` 關鍵邏輯


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 集成
線程安全需鎖保護護衛區檢查需原子操作計數器

進階替代方案(生產環境推薦)

  • AddressSanitizer (ASan):`-fsanitize=address` 編譯器插桩,編譯期/運行期自動檢測越界、洩漏、Use-after-free、雙重釋放,性能損耗 ~2x,Google/Chrome/LLVM 主力工具。
  • Valgrind Memcheck:二進制插桩,無需重新編譯,功能全面但慢 20-50x。
  • Electric Fence / DUMA:利用虛擬內存頁保護(`mprotect`)在頁邊界放置不可訪問護衛頁,硬件級捕獲越界,精準但內存開銷大。

總結

**「邊界標記法」守護每次分配的邊界,「全局計數法」守護整個程序的平衡**。兩套輕量代碼(各 ~100 行)即可構建 C 項目內存安全防線。開發階段建議:Debug 構建啟用全局計數 + 關鍵模塊啟用邊界標記;發布前跑一遍 ASan/Valgrind 全量掃描。內存安全,從「碰運氣」變成「有保障」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved