《C 程序設計語言》第 8 章給出的 malloc/free 簡易實現,展示了動態內存分配器的核心數據結構:**空閒塊鏈表**、header 元數據、**首次適應** 算法。本文逐行解析。
typedef long Align; // 強制對齊最受限類型 (通常 long/double)
union header { // 每個塊頭部
struct {
union header *ptr; // 下一空閒塊指針
unsigned size; // 本塊大小 (含 header,單位:header 個數)
} s;
Align x; // 強制對齊
};
typedef union header Header;
static Header base; // 空鏈表錨點 (size=0)
static Header *freep = NULL; // 當前掃描起始指針
關鍵點:
base 作為永久錨點,freep 記錄上次分配位置,下次從這裡開始掃描 (提高局部性)。void *malloc(unsigned nbytes) {
Header *p, *prevp;
unsigned nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1; // 向上取整 + header
if ((prevp = freep) == NULL) { // 首次調用初始化
base.s.ptr = freep = prevp = &base;
base.s.size = 0;
}
for (p = prevp->s.ptr; ; prevp = p, p = p->s.ptr) {
if (p->s.size >= nunits) { // 找到足夠大塊
if (p->s.size == nunits) // 精確匹配:從鏈表摘除
prevp->s.ptr = p->s.ptr;
else { // 分割:從尾部割出用戶塊,縮小原塊
p->s.size -= nunits;
p += p->s.size; // p 指向新分配塊的 header
p->s.size = nunits;
}
freep = prevp; // 更新掃描起點
return (void*)(p + 1); // 返回用戶數據區 (跳過 header)
}
if (p == freep) // 掃描一圈回到起點,無足夠塊
if ((p = morecore(nunits)) == NULL)
return NULL; // 向系統申請失敗
}
}
#define NALLOC 1024 // 最小申請單元 (header 個數)
Header *morecore(unsigned nu) {
if (nu < NALLOC) nu = NALLOC;
Header *up = (Header*)sbrk(nu * sizeof(Header)); // 系統調用擴展堆
if (up == (Header*)-1) return NULL;
up->s.size = nu;
free((void*)(up + 1)); // 將新塊加入空閒鏈表 (free 會合併相鄰塊)
return freep;
}
sbrk 增加進程數據段斷點,獲取原始內存。free 插入鏈表,利用 free 的合併邏輯處理相鄰情況。void free(void *ap) {
Header *bp, *p;
bp = (Header*)ap - 1; // 回退到 header
// 找到插入位置:按地址升序維護鏈表
for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
if (p >= p->s.ptr && (bp > p || bp < p->s.ptr)) // 跨越鏈表末尾/開頭
break;
// 嘗試與後塊合併
if (bp + bp->s.size == p->s.ptr) {
bp->s.size += p->s.ptr->s.size;
bp->s.ptr = p->s.ptr->s.ptr;
} else
bp->s.ptr = p->s.ptr;
// 嘗試與前塊合併
if (p + p->s.size == bp) {
p->s.size += bp->s.size;
p->s.ptr = bp->s.ptr;
} else
p->s.ptr = bp;
freep = p; // 更新掃描起點
}
| 特性 | 實現 | 優缺點 |
|---|---|---|
| 分配策略 | 首次適應 | 快,但易產生外部碎片 |
| 釋放合併 | 立即合併前後相鄰塊 | 減少碎片,O(1) 額外開銷 |
| 元數據開銷 | 1 Header/塊 (~16-32 字節) | 小塊分配開銷大 |
| 線程安全 | 無 (全局 freep) | 多線程需加鎖或用線程本地緩存 |
| 對齊保證 | union header + Align | 滿足最受限類型對齊 |