本文通過實驗驗證參數入棧順序,深度解析 C 語言調用約定設計動機,揭示可變參數支持與棧幀佈局的本質關聯。
void foo(int x, int y, int z) {
printf("x = %d at [%p]\n", x, &x);
printf("y = %d at [%p]\n", y, &y);
printf("z = %d at [%p]\n", z, &z);
}
foo(100, 200, 300);
輸出 (棧增長方向:高地址→低地址):
x = 100 at [0x7ffc1234] // 最高地址 y = 200 at [0x7ffc1238] z = 300 at [0x7ffc123c] // 最低地址
證明:參數從右至左入棧(z 最先入棧,位於棧頂低地址)。
關鍵洞察:
// printf 格式化字符串在首參數固定位置 void printf(const char *fmt, ...); // fmt 地址固定 = 幀指針 + 偏移 // 後續可變參數向高地址延伸,通過 fmt 解析獲取個數/類型
若左→右入棧,首參數位置隨參數個數變動,無法定位格式化字符串。
| 約定 | 參數順序 | 棧清理 | 可變參數 | 用途 |
|---|---|---|---|---|
| __cdecl (C默認) | 右→左 | 調用者 | 支持 | 標準C函數 |
| __stdcall (Pascal) | 右→左 | 被調用者 | 不支持* | Win32 API |
| __fastcall | 前兩寄存器其餘右→左 | 被調用者 | 不支持 | 性能關鍵 |
| thiscall | 右→左(this在ECX) | 被調用者 | 不支持 | C++成員函數 |
* __stdcall 加 VARARG 退化為 __cdecl。
__cdecl:_funcname__stdcall:_funcname@bytes__fastcall:@funcname@bytesC 語言選擇右向左入棧,本質是為了在編譯期未知參數個數時,仍能通過固定偏移訪問首參數(格式化字符串/參數計數器),這是支持 printf 等可變參數函數的基礎架構決策。