C語言函數參數採用「從右往左」壓入棧中,這不是任意決定,而是為了支援可變參數而做的關鍵設計選擇。本文深入剖析其底層機制與歷史背景。
#include <stdio.h>
void foo(int x, int y, int z) {
printf("x = %d at [%p]\n", x, (void*)&x);
printf("y = %d at [%p]\n", y, (void*)&y);
printf("z = %d at [%p]\n", z, (void*)&z);
}
int main() {
foo(100, 200, 300);
return 0;
}
x = 100 at [0x7ffeefbff5ac] // 最高位址 y = 200 at [0x7ffeefbff5a8] z = 300 at [0x7ffeefbff5a4] // 最低位址(最後壓入)
參數 z(最右邊)位於最低位址,證實「從右往左」入棧。
| 入棧順序 | 第一個參數位置 | 可變參數可行性 |
|---|---|---|
| 左→右 (Pascal) | 棧底(固定) | ❌ 不可行:無法定位第一個參數 |
| 右→左 (C) | 棧頂附近(固定偏移) | ✅ 可行:第一個參數位置固定 |
關鍵洞見:
printf(fmt, ...) 中,fmt 是第一個參數,位置固定fmt 字串,得知後續參數個數與類型fmt 會被壓在棧底,無法透過固定偏移存取| 慣例 | 參數順序 | 棧平衡者 | 用途 | 名稱修飾 |
|---|---|---|---|---|
| __cdecl (C預設) | 右→左 | 呼叫者 | 可變參數 | _func |
| __stdcall (WinAPI) | 右→左 | 被呼叫者 | 固定參數 | _func@N |
| __fastcall | 前兩個用暫存器 | 被呼叫者 | 效能優化 | @func@N |
| thiscall | 右→左, this在ECX | 被呼叫者 | C++ 成員函數 | 編譯器內部 |
stdarg.h),鎖定右→左為事實標準func(f(), g()) 中 f/g 呼叫順序未定義C 語言參數右→左入棧,本質上是為了在編譯期不確定參數個數時,仍能以固定偏移存取第一個參數。這個設計決策直接成就了 printf、scanf 等可變參數函數,也深遠影響了後續的呼叫慣例標準化。