頻繁 malloc/free 導致內存碎片、分配開銷大、洩漏難查、多線程鎖競爭。本文設計一個定長塊內存池,採用位圖管理空閒塊,實現 O(1) 分配釋放、零碎片、內建洩漏統計、邊界保護,適合嵌入式、高頻分配、實時系統場景。
#include
#include
#include
typedef int Status;
#define TRUE 1
#define FALSE 0
// 內存池控制塊
typedef struct {
void *pool_mem; // 大塊內存起始地址 (含數據區 + 位圖區)
char *bitmap; // 位圖指針 (指向 pool_mem 後的位圖區)
void *data_area; // 數據區起始地址 (對齊後)
size_t block_size; // 单塊大小 (含用戶數據 + 可選頭部)
size_t block_count; // 總塊數
size_t free_count; // 空閒塊數 (快速統計)
size_t align; // 對齊要求
} MemPool;
MemPool* mempool_create(size_t block_size, size_t block_count, size_t align) {
if (block_size == 0 || block_count == 0) return NULL;
align = (align == 0) ? sizeof(void*) : align; // 默認指針對齊
// 1. 計算位圖大小 (每塊 1 bit,向上取整到字節)
size_t bitmap_bytes = (block_count + 7) / 8;
// 2. 計算總內存大小:控制塊 + 數據區(對齊) + 位圖區
// 數據區起始需對齊
size_t data_offset = (sizeof(MemPool) + align - 1) & ~(align - 1);
size_t total_size = data_offset + block_count * block_size + bitmap_bytes;
// 3. 一次性分配
void *mem = malloc(total_size);
if (!mem) return NULL;
memset(mem, 0, total_size);
// 4. 初始化控制塊 (放在最開頭)
MemPool *pool = (MemPool*)mem;
pool->block_size = block_size;
pool->block_count = block_count;
pool->free_count = block_count;
pool->align = align;
// 5. 設置區域指針
pool->data_area = (char*)mem + data_offset;
pool->bitmap = (char*)mem + data_offset + block_count * block_size;
return pool;
}
void* mempool_alloc(MemPool *pool) {
if (!pool || pool->free_count == 0) return NULL;
// 掃描位圖字節
for (size_t byte_idx = 0; byte_idx < (pool->block_count + 7) / 8; byte_idx++) {
unsigned char byte = pool->bitmap[byte_idx];
if (byte == 0xFF) continue; // 該字節 8 塊全滿
// 逐位檢查
for (int bit = 0; bit < 8; bit++) {
size_t block_idx = byte_idx * 8 + bit;
if (block_idx >= pool->block_count) break;
if ((byte & (1 << bit)) == 0) { // 找到空閒
// 標記佔用
pool->bitmap[byte_idx] |= (1 << bit);
pool->free_count--;
return (char*)pool->data_area + block_idx * pool->block_size;
}
}
}
return NULL; // 理論上不會到達
}
Status mempool_free(MemPool *pool, void *ptr) {
if (!pool || !ptr) return FALSE;
// 1. 地址範圍檢查
char *start = (char*)pool->data_area;
char *end = start + pool->block_count * pool->block_size;
char *p = (char*)ptr;
if (p < start || p >= end) return FALSE; // 非本池指針
// 2. 對齊檢查 (必須是塊起始地址)
size_t offset = p - start;
if (offset % pool->block_size != 0) return FALSE; // 非法指針 (內部指針)
size_t block_idx = offset / pool->block_size;
// 3. 重複釋放檢查 (位圖已為 0)
size_t byte_idx = block_idx / 8;
int bit = block_idx % 8;
if ((pool->bitmap[byte_idx] & (1 << bit)) == 0) return FALSE; // 雙重釋放
// 4. 標記空閒
pool->bitmap[byte_idx] &= ~(1 << bit);
pool->free_count++;
// 可選:清零數據區 (安全/調試模式)
// memset(p, 0, pool->block_size);
return TRUE;
}
void mempool_destroy(MemPool *pool) {
if (pool) free(pool); // 一次性釋放整個大塊
}
size_t mempool_free_count(const MemPool *pool) {
return pool ? pool->free_count : 0;
}
size_t mempool_used_count(const MemPool *pool) {
return pool ? pool->block_count - pool->free_count : 0;
}
| 特性 | 實現要點 |
|---|---|
| 變長塊分配 | 多級池 (如 16/32/64/128/256/512/1024/2048 Bytes),請求按尺寸路由到最小滿足池 |
| 最佳適配 | 維護空閒塊鏈表按地址排序,分配時尋找最小滿足塊 (減碎片),釋放時合併相鄰空閒塊 |
| 線程安全 | 每池一把自旋鎖 / 無鎖環形隊列 (單生產單消費) / Thread-Local Cache (TCMalloc 風格) |
| 調試模式 | 塊頭添加魔數 0xDEADBEEF、分配棧跡、紅區、雙重釋放檢測、洩漏報告 (析構時 free_count != block_count 報警) |
| 內存對齊保證 | posix_memalign 分配大塊,data_area 按 max_align_t 對齊,滿足 SIMD/原子操作要求 |
| 統計監控 | 峰值使用量、分配/釋放次數、平均塊壽命、碎片率導出 Prometheus 指標 |
| 測試 | 描述 | 預期 |
|---|---|---|
| 基本分配釋放 | 循環 alloc/free 10000 次 | 無洩漏、free_count 歸零 |
| 滿池壓力 | 分配至 free_count=0,再 alloc 失敗 | 返回 NULL、不崩潰 |
| 雙重釋放 | free 同一指針兩次 | 返回 FALSE、不崩潰 |
| 非法指針 | free 堆指針、棧指針、池內偏移指針 | 返回 FALSE |
| 併發壓力 | 多線程並發 alloc/free (加鎖版) | 無數據競爭、計數一致 |
| 對齊驗證 | 分配指針 % align == 0 | 全通過 |
基於位圖的定長塊內存池,以「一次大分配、位圖 O(1) 管理、零碎片、內建統計」的極簡架構,解決了高頻小塊分配的核心痛點。理解其佈局計算、位圖掃描、地址合法性驗證三大核心邏輯,即可靈活擴展為多級池、無鎖池、調試池,成為系統級內存管理的基石組件。