許多面試題問「參數是從左到右還是右到左壓棧?」標準答案是:**視架構與呼叫慣例而定**。
#include <stdio.h>
void test(int a, int b) {
printf("&a = %p\n", (void*)&a);
printf("&b = %p\n", (void*)&b);
if ((uintptr_t)&a > (uintptr_t)&b)
puts("Left-to-right (a 位址較高 → 先壓入)");
else
puts("Right-to-left (b 位址較高 → 先壓入)");
}
int main(void) { test(1, 2); return 0; }
| 平台 | 呼叫慣例 | 入棧順序 | 說明 |
|---|---|---|---|
| 32-bit x86 (Linux/Windows) | cdecl | 右到左 | 呼叫者清理堆疊,方便可變參數 |
| 64-bit x86-64 (Linux, System V AMD64 ABI) | System V AMD64 | 左到右 (概念上) | 前 6 整數參數放暫存器 (RDI, RSI, RDX, RCX, R8, R9),其餘才壓棧;棧上順序與宣告順序一致 |
| 64-bit x86-64 (Windows, Microsoft x64) | Microsoft x64 | 左到右 | 前 4 參數放暫存器 (RCX, RDX, R8, R9),影子空間預留 |
以 System V AMD64 ABI 為例:
RDI, RSI, RDX, RCX, R8, R9RBP 相對位移); 呼叫端 main
mov esi, 2 ; 第二參數 → RSI
mov edi, 1 ; 第一參數 → RDI
call test
; 被呼叫端 test
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi ; a 存於 rbp-4
mov DWORD PTR [rbp-8], esi ; b 存於 rbp-8 (位址較低)
; 此時 &a > &b,呈現左到右
; 呼叫端
push 2 ; 先壓第二參數
push 1 ; 再壓第一參數
call test
add esp, 8 ; 呼叫者清理
; 被呼叫端
push ebp
mov ebp, esp
; [ebp+8] = a (第一參數,位址較高)
; [ebp+12] = b (第二參數,位址較低)
; &a > &b,右到左入棧
[參數 n] ... [參數 2] [參數 1] [返回位址] [舊 EBP/RBP] [區域變數] ...
printf 等)之所以要求右到左(cdecl),是為了讓固定參數位置固定,方便 va_start 定位