呼叫慣例
想像兩個人必須來回交付工作卻從不碰面約定規則:一個準備包裹,另一個拆開它。如果他們不共用一套固定協定——包裹放哪裡、由誰搬運、由誰善後——這次交接就會失敗。呼叫慣例正是呼叫函式時的這套共用協定:引數如何傳遞、回傳值從何處取回、跨越呼叫時誰負責哪些暫存器的精確規則。
這套慣例釘死了好幾件事。引數如何傳遞:前幾個通常放進特定暫存器,只有多出來的才溢到堆疊上。在常見的 x86-64 System V 慣例(Linux、macOS)中,前六個整數引數依序放入 rdi、rsi、rdx、rcx、r8、r9,整數回傳值由 rax 帶回。它也固定了暫存器的責任歸屬(呼叫端保存對被呼叫端保存)、呼叫時堆疊必須具備的對齊,以及像紅區這樣的小細節——堆疊頂端外側的一塊 128 位元組區域,葉子函式可以不調整 rsp 就在其中塗寫。這套慣例是更大的 ABI(應用二進位介面)的一部分,ABI 還涵蓋型別如何佈局、可執行檔如何構成。
其全部要點在於:分別編譯的各部分——你的程式碼、一個函式庫、C 執行期——對這些規則達成一致,好讓它們在從不見彼此原始碼的情況下互通。正是它讓一種語言寫的函式能呼叫另一種語言寫的函式,也讓你能連結預先建好的函式庫。違反慣例(比方用了錯誤的函式指標簽名),你得到的是毀損而非乾淨的錯誤,因為雙方各自在讀寫對方從未同意的暫存器與堆疊格。
在 x86-64 System V 下呼叫 f(1, 2) 會把 1 放進 rdi、2 放進 rsi,然後 call f;f 把它的 int 結果由 rax 回傳。
慣例固定了引數放哪、結果從何回來,以及每個暫存器歸誰所有。
並無單一通用慣例:x86-64 的 Windows 使用與 System V 不同的引數暫存器(rcx、rdx、r8、r9),ARM 又另一套。紅區是個真實又容易遺忘的細節——信號處理常式或中斷絕不可假設 rsp 外側那 128 位元組是空的。