堆疊式配置器
想像一疊盤子。你把盤子加到最上面,也從最上面拿走——後放的先拿。堆疊式配置器(stack allocator)把這套紀律帶進記憶體:它是一個也支援釋放的 arena,但只能以與配置完全相反的順序釋放。你可以歸還記憶體,但只能歸還最近配置的那一塊,接著是次近的,依此類推。它是純推進 arena(直到最後才釋放任何東西)與通用配置器(可任意順序釋放任何東西)之間的折衷。
它運作起來就像一個多了一樣東西的推進配置器:一個標記(marker)。配置會把頂端指標往前移,就跟 arena 一樣。但配置器也讓你把目前的頂端存成一個標記,之後再釋放回到那個標記,這會把標記之後配置的一切瞬間回收,方法只是把頂端指標移回所存的位置。這正是巢狀範圍的行為:進入一個區域,記住標記;在裡面自由配置;離開時回捲到標記,以常數時間釋放整批。程式自己的呼叫堆疊正是依此原理運作(函式的區域變數在它返回時消失),名稱即由此而來。
堆疊式配置器在臨時、巢狀的計算中大放異彩:函式內的暫存空間、每層需要工作緩衝區的遞迴演算法、或是在畫格開始時推入標記、畫格結束時彈出的每畫格配置器。它們極快且不產生碎片,因為釋放總是發生在頂端。嚴格的限制就是 LIFO 規則本身:若物件 A 在物件 B 之前配置,但 A 必須先被釋放,堆疊式配置器根本辦不到——釋放 A 需要搬動 B,而它不會這麼做。當生命期不呈巢狀時,你得改用 pool 或通用配置器。
size_t mark = stack_get_marker(s); /* 記住頂端 */ void *a = stack_alloc(s, 100); void *b = stack_alloc(s, 200); /* ... 使用 a 與 b ... */ stack_free_to(s, mark); /* 一次釋放 b 接著 a */ /* 想在 b 之前釋放 a,依設計即不可能 */
存一個標記、自由配置,再把頂端回捲到標記,以 LIFO 順序一次釋放整批。
這裡的堆疊式配置器是一種記憶體區域技術;它與程式的呼叫堆疊不是同一回事,雖然共用 LIFO 的想法。它的硬性規則是釋放必須以相反順序鏡像配置。