組合語言與 CPU

呼叫端保存對被呼叫端保存的暫存器

兩個人共用一塊白板。如果你在上面寫了個數字,然後請同事去做件小事,等他回來時你的數字還在嗎?只有在你們事先約好誰能擦掉什麼的情況下才在。暫存器就是呼叫端函式與它所呼叫之函式之間的那塊共用白板,而呼叫端保存對被呼叫端保存,正是關於跨越呼叫時誰必須保留哪個暫存器的那項約定。

被呼叫端保存的暫存器(也叫非揮發性或受保留)是被呼叫函式承諾保持不變的:若它要使用該暫存器,必須先存下舊值,並在返回前還原。所以呼叫端可以把一個值留在被呼叫端保存的暫存器中、發出一次呼叫,並信任該值會存活。呼叫端保存的暫存器(也叫揮發性或暫用)正相反:被呼叫函式可以隨意破壞它。若呼叫端在呼叫端保存的暫存器中有個存活的值、並在呼叫後還需要它,呼叫端必須在呼叫前自行保存(通常推上堆疊)並在之後還原。在 x86-64 System V 上,rbx、rbp 以及 r12 到 r15 是被呼叫端保存;rax、rcx、rdx、rsi、rdi 以及 r8 到 r11 是呼叫端保存。

這個劃分是呼叫慣例對一個真實張力的解答:每次呼叫都保存每個暫存器太浪費,但一個都不保存則代表某個值永遠無法在呼叫後存活。這個劃分讓每一方只保存自己非保不可的。閱讀反組譯時,函式開頭對 rbx 或 r12 的推入、結尾對它們的彈出,正是這條規則在運作——函式借用一個被呼叫端保存的暫存器並盡責地還原它。把責任弄反是個微妙的錯誤,手寫或誤編譯的組合語言可能引入它,悄悄毀損呼叫端以為安全的值。

要讓一個值在呼叫後存活?把它留在像 rbx 這樣的被呼叫端保存暫存器裡——但你的函式接著必須在序言中 push rbx、在收尾中 pop rbx,以對呼叫你的人履行承諾。

被呼叫端保存的暫存器能在呼叫後存活(被呼叫端會保留它們);呼叫端保存的可能被破壞。

哪個暫存器屬於哪一組是由慣例固定的,不是你選的,且各 ABI 不同——同一個暫存器在一個平台上可能是呼叫端保存,在另一平台上是被呼叫端保存。「呼叫端保存」意指若有需要由呼叫端負責保存它,並非有誰會自動保存。

又稱
volatile vs non-volatile registersscratch vs preserved registers揮發性對非揮發性暫存器