組合語言與程序呼叫

堆疊框

每當一個函式開始執行,它都需要一塊私人的草稿區:放區域變數的空間、藏返回位址的地方、保存它借用的任何被呼叫者保存暫存器的位置。這塊屬於某一個正在進行的呼叫的私人堆疊區塊,就是它的堆疊框(stack frame,也叫活動記錄,activation record)。函式返回時,這個框就被丟棄,空間還回池子。想像一疊自助餐托盤:每個呼叫滑上一個帶著自己餐盤的新托盤,做完就把它收走。

框在進入時搭建、在離開時拆除。進入時函式執行一小段序言(prologue):把堆疊指標減去一些以配置這個框,再把它必須保住的東西(返回位址、它將用到的任何被呼叫者保存暫存器)存進框裡。本體接著在離堆疊指標固定位移處讀寫它的區域變數。離開時一段尾聲(epilogue)把一切倒過來做:重新載入保存的暫存器、把堆疊指標加回去以釋放這個框,然後返回。因為每個呼叫的框就坐落在它呼叫者的框正下方,這些框自然形成硬體指標所追蹤的那同一個後進先出堆疊。

堆疊框之所以重要,是因為它正是讓巢狀與遞迴呼叫得以運作的關鍵:每個呼叫都拿到一份全新、獨立的區域變數副本,所以函式呼叫自己時不會踩壞自己的變數。它們也解釋了一些熟悉的現象。堆疊溢位(stack overflow)不過就是堆了太多框(常是失控的遞迴),蓋過了保留給堆疊的空間。而除錯器的回溯(backtrace)也只是沿著框鏈走一遍、讀每個保存的返回位址,重建出誰呼叫了誰。

一個有 16 位元組框的函式: f: addi sp, sp, -16 # 序言:配置框 sd ra, 8(sp) # 把返回位址存進框裡 sd s0, 0(sp) # 保存一個被呼叫者保存暫存器 ... # 本體把 0(sp) 與 8(sp) 當作它的私有插槽使用 ld s0, 0(sp) # 尾聲:還原 ld ra, 8(sp) addi sp, sp, 16 # 釋放框 ret

序言配置並保存;本體用固定位移;尾聲還原並釋放。框的大小被精準地平衡掉。

每個正在進行的呼叫都有自己的框,這正是遞迴能運作的原因。堆疊溢位通常表示框一直堆積卻不返回,最常見的原因是遞迴沒有觸及基本情況。

又稱
activation record活動記錄活化記錄