free() 並不依賴指標類型(int\*、char\* 等)決定大小,而是從記憶體塊頭部的隱藏元數據中讀取塊大小。
+------------------+------------------+------------------+
| 分配器頭部元數據 | 用戶可用區域 | 可能的對齊填充 |
| (size, flags...) | ← malloc 返回指標 | |
+------------------+------------------+------------------+
↑
實際起始地址 (malloc 內部記錄)
p 指向 **用戶可用區域起始**。p - header_size 視為塊真實起始,頭部存儲 **塊大小、魔數、空閒標記、前後塊指標** 等。free(p) 時,分配器向前偏移找到頭部,**讀取 size 字段**,據此將塊標記為空閒並合併相鄰空閒塊。int *p1 = malloc(4); // 分配 4 字節(或更多對齊)
char *p2 = malloc(4); // 同樣分配 4 字節
free(p1); free(p2); // 兩者釋放相同大小,因頭部記錄的均為實際分配大小
sizeof(*p1) 編譯期僅知為 4,但 malloc 運行期可能分配 16/32 字節(對齊、最小塊限制)。**釋放大小以運行期分配器記錄為準,而非編譯期類型。**
| 分配器 | 頭部大小 | 特點 |
|---|---|---|
| glibc ptmalloc | 8/16 字節 (64/32位) | 多執行緒 arena、fastbin、tcache、大塊 mmap |
| Windows Heap | 8 字節 + 可選 | 前端 LFH (Low Fragmentation Heap)、後端段管理 |
| dlmalloc (嵌入式常用) | 4/8 字節 | 邊界標記、雙向循環空閒鏈表 |
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = malloc(100);
printf("User ptr: %p\n", (void*)p);
// 假設 64 位 glibc,頭部在 p - 8 字節 (size_t)
size_t *header = (size_t *)((char *)p - sizeof(size_t));
printf("Recorded size (with flags): %zu\n", *header);
free(p);
return 0;
}
注意:直接讀寫元數據屬於未定義行為,僅供理解原理,生產代碼嚴禁。
sizeof(*ptr) 推斷 malloc 大小——錯誤且危險。size_t len 字段,或用 malloc_usable_size(p) (glibc) / _msize(p) (Windows) 獲取實際可用大小。malloc/calloc/realloc 必須對應一次 free,避免洩漏/重複釋放。free(p); p = NULL; 防止野指標二次釋放。