C 標準庫在管理堆內存分配時,以 chunk 作為基本邏輯單元。每個 chunk 在內存中呈現為一個結構體,包含大小、指針等元數據:
struct malloc_chunk { size_t prev_size; // 前一個 chunk 的大小(僅在前一個 chunk 空閒時有效) size_t size; // 當前 chunk 大小(低 3 位用作標誌位) struct malloc_chunk* fd; // 雙向鏈表前向指針(空閒時) struct malloc_chunk* bk; // 雙向鏈表後向指針(空閒時) struct malloc_chunk* fd_nextsize; // 大塊內存鏈表指針 struct malloc_chunk* bk_nextsize; // 大塊內存鏈表指針 }; 狀態切換邏輯:當 chunk 處於空閒狀態時,fd 和 bk 構成雙向鏈表,便於快速分配與合併;一旦被分配,size 之後的空間即成為用戶數據區,fd/bk 失去原意。size 字段記錄用戶實際申請的大小,prev_size 則用於快速定位相鄰 chunk,實現 O(1) 的合併操作。
glibc 定義了一系列宏簡化 chunk 與用戶指針互轉(假設 SIZE_SZ = sizeof(size_t)):
chunk2mem(p) = (void*)((char*)(p) + 2*SIZE_SZ):從 chunk 頭部跳過兩個 size_t 得到用戶指針。mem2chunk(mem) = (mchunkptr)((char*)(mem) - 2*SIZE_SZ):反向操作。由於 chunk 按 8 字節對齊,size 低 3 位恆為 0,被用作標誌位:
PREV_INUSE (0x1):前一個 chunk 是否在用。IS_MMAPPED (0x2):是否由 mmap 分配。常用導航宏:
#define next_chunk(p) ((mchunkptr)(((char*)(p)) + ((p)->size & ~PREV_INUSE)))#define prev_chunk(p) ((mchunkptr)(((char*)(p)) - ((p)->prev_size)))#define inuse(p) ((((mchunkptr)(((char*)(p))+((p)->size & ~PREV_INUSE)))->size) & PREV_INUSE) 分配器維護一個 malloc_state 結構(主 arena 為全局變量 main_arena),核心字段包括:
fastbins[NFASTBINS]:小塊快速緩存(單向鏈表,LIFO)。top:頂塊指針,剩餘最大連續內存區。bins[NBINS*2-2]:常規 bins(雙向循環鏈表),按大小分類,支持 best-fit。binmap:位圖加速非空 bin 查找。分配策略分級:
fd_nextsize/bk_nextsize 加速大小查找。request2size(req) 將用戶請求轉為含元數據的 chunk 大小,最小 MINSIZE(32 位機 16B,64 位機 32B),確保至少容納 prev_size, size, fd, bk 四字段。fastbin_index(nb) = (nb>>3) - 2,直接從對應單向鏈表頭取塊,驗證大小一致性後返回。sysmalloc 向系統申請(brk 或 mmap)。malloc_trim 歸還內存給 OS。fastbin_index(chunksize(victim)) == idx。check_remalloced_chunk、malloc_printerr。perturb_byte 可選填充新分配/釋放內存,暴露未初始化讀寫。理解上述機制有助於診斷堆損壞、優化內存碎片、編寫高性能分配器或進行二進制漏洞利用分析。