框架指標
在函式內部,你的區域變數住在離堆疊上某處固定位移的地方。最自然的錨點是堆疊指標,但堆疊指標在函式執行期間可能移動(例如當你為某次呼叫推入引數時),於是同一個區域變數的位移會時刻不同。框架指標(frame pointer)的解法是:持有一個指向目前框起點的固定參考,在整個呼叫期間都不移動。這就像把一把捲尺釘在房間門口,讓每次測量都從同一個零點起算,即使你在屋裡搬動家具也一樣。
進入時,序言會保存舊的框架指標,並把框架指標設為標記這個框的基底;從此每個區域變數都在離框架指標固定位移處,不管堆疊指標怎麼晃動。保存下來的框架指標還會串接起來:每個框存著上一個框架指標,形成一條框的鏈結串列,除錯器可以沿著它走來產生回溯。離開時,框架指標被還原成呼叫者的值。在 RISC-V 上,用到框架指標時它是暫存器 s0(也叫 fp),而因為它是被呼叫者保存的,所以會自動跨呼叫保留。
框架指標主要對清晰度與可除錯性有用,但這裡有誠實的一面:它是可選的。現代編譯器能用它們精確追蹤的堆疊指標來算出每個區域變數的位置,從而把框架指標暫存器空出來作一般用途(在速度與暫存器壓力上有點小好處);這叫框架指標省略(frame-pointer omission)。代價是省略它會讓走堆疊變難,這就是為什麼效能分析器與當機除錯器常請你保留框架指標。所以它是一種便利與除錯輔助,而非硬體上的必需。
用 s0/fp 當穩定的錨點: g: addi sp, sp, -16 sd ra, 8(sp) sd s0, 0(sp) # 保存呼叫者的框架指標 addi s0, sp, 16 # s0 現在標記這個框的頂端,在整個呼叫期間固定不動 ... # 區域變數以 -N(s0) 定址;sp 可能移動,s0 不會 ld s0, 0(sp) # 還原呼叫者的框架指標 ld ra, 8(sp) addi sp, sp, 16 ret
即使堆疊指標在呼叫期間移動,框架指標仍讓區域變數有固定的位址。
框架指標是便利而非必需:編譯器常省略它以空出一個暫存器,代價是讓除錯器與效能分析器更難展開堆疊。