自訂記憶體配置器

自由串列(隱式與顯式)

當你 free() 一個區塊時,配置器並不會把記憶體還給作業系統——它留著,好讓下一次 malloc() 能立刻重用。但要重用一個已釋放的區塊,它必須記住該區塊存在、以及它在哪裡。自由串列(free list)就是這份對所有目前空閒區塊的記憶。想像一間旅館保有一份哪些房間是空的清單:有客人入住時,你掃過清單、找到合適的空房、把它劃掉;客人離開時,再把房間加回去。自由串列就是配置器的空房清單。

保存它有兩種經典方式,差別在於記帳資料放在哪裡。隱式自由串列(implicit free list)根本不另存一份清單:而是整個區域裡的每一個區塊都帶一個小標頭,記著它的大小與一個空閒/使用中的位元,一個接一個地排列;於是要找空閒區塊,你就從頭逐塊走過,讀每個標頭並按其大小往前跳——空閒區塊是靠掃描隱含推得的。顯式自由串列(explicit free list)更聰明:它只把空閒的區塊串成一條鏈。關鍵在於,既然空閒區塊的酬載(payload)反正沒在用,配置器就把 next 與 prev 指標存進那個已釋放區塊自己的本體裡。於是 malloc() 只走空閒區塊這條鏈(而非每一個區塊),而 free() 把區塊接回鏈中。當堆積大部分都已配置時,顯式串列快得多,因為你跳過所有忙碌的區塊而不去檢視它們。

這是幾乎每個通用配置器跳動的心臟。後續的設計選擇——串列如何排序、是一條串列還是多條(分離式)、如何決定要交出哪個空閒區塊(first-fit、best-fit)——全都是這一個想法的精煉。誠實的提醒:走訪自由串列並非免費。一條巨大的串列會讓 malloc() 變慢(與空閒區塊數成線性),這正是正式配置器把它按大小拆成許多分離式串列的原因。

/* 顯式自由串列:指標就住在已釋放區塊的本體裡 */ struct free_block { size_t size; /* 標頭:大小 + 空閒位元 */ struct free_block *next; /* 存在沒在用的酬載中 */ struct free_block *prev; }; /* malloc 走訪:head -> next -> next ... 直到某區塊夠大 */

顯式自由串列把 next/prev 指標穿過空閒區塊本身的本體串接起來。

把串列指標存在空閒區塊裡之所以安全,正因為該區塊是空閒的;malloc() 一旦把它交出去,那些位元組馬上又變回程式的酬載。這就是內嵌自由串列指標的技巧。

又稱
free listfreelist空閒區塊串列