首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言函數調用約定全景解析:stdcall、cdecl、fastcall、thiscall 與 naked call

     編輯:關於C語言

核心問題:機器碼層面如何傳參?

高級語言函數調用編譯為機器碼時,CPU 不知道參數個數、類型,無硬件自動保存參數。雙方需約定:

  1. 參數入棧順序:左→右 或 右→左?
  2. 棧平衡責任:調用者清理 或 被調用者清理?

這兩點定義了「調用約定」。本文以 x86 32 位為例深度剖析五大主流約定。

一、棧幂基礎知識回顧

  • 棧:先進後出 (LIFO),高地址→低地址增長。
  • ESP:棧頂指針。EBP:幂幂基址指針 (幂幂框架指針)。
  • PUSH:ESP-=4; [ESP]=val。POP:val=[ESP]; ESP+=4。
  • 函數調用 CALL:隱式 PUSH EIP (返回地址) 並跳轉。
  • 函數返回 RET n:POP EIP; ESP+=n (清理參數)。

二、五大調用約定詳細對比

1. __stdcall (Pascal / WINAPI / CALLBACK)

  • 參數順序:右→左 入棧。
  • 棧清理:被調用者 (RET n)。
  • 名字修飾:_FuncName@ParamBytes (如 _function@8)。
  • 適用場景:Windows API、COM 接口、固定參數個數的 DLL 導出。
// C/C++ 聲明
int __stdcall Add(int a, int b);

// 調用端彙編
push 2      ; 右參先入
push 1      ; 左參後入
call Add

; 被調用端彙編
Add:
    push ebp
    mov  ebp, esp
    mov  eax, [ebp+8]   ; 參數 a (EBP+8: 舊EBP, 返回地址, a, b)
    add  eax, [ebp+0Ch] ; 參數 b
    pop  ebp
    ret  8              ; 清理 8 字節參數

2. __cdecl (C Default)

  • 參數順序:右→左 (同 stdcall)。
  • 棧清理:調用者 (ADD ESP, n)。
  • 名字修飾:_FuncName (僅前導下劃線,MSVC)。
  • 關鍵特性:支持可變參數 (如 printf)。因調用者知曉參數總大小,可動態清理。
; 調用端
push 2
push 1
call _Add
add  esp, 8      ; 調用者清理

; 被調用端
_Add:
    push ebp
    mov  ebp, esp
    mov  eax, [ebp+8]
    add  eax, [ebp+0Ch]
    pop  ebp
    ret            ; 無立即數,不清理棧

3. __fastcall

  • 參數傳遞:前兩個 DWORD (或更小) 參數通過 ECX、EDX 傳遞,其餘右→左入棧。
  • 棧清理:被調用者 (RET n)。
  • 名字修飾:@FuncName@ParamBytes。
  • 優勢:減少內存訪問,提升調用速度。

4. __thiscall (C++ 成員函數默認)

  • 不可顯式指定 (非關鍵字),編譯器自動處理。
  • 固定參數:this 指針放 ECX,參數右→左入棧,被調用者清理 (類 stdcall)。
  • 可變參數:this 最後入棧 (所有參數之後),調用者清理 (類 cdecl)。
class A { int f1(int a, int b); int f2(int a, ...); };
A a; a.f1(1,2); a.f2(3,1,2,3);

; f1 調用 (固定參數)
push 2
push 1
lea  ecx, [a]   ; this 在 ECX
call f1

; f2 調用 (可變參數)
push 3          ; 參數 3
push 2
push 1
push 3          ; 參數 a (計數)
lea  eax, [a]
push eax        ; this 最後入棧
call f2
add  esp, 14h   ; 調用者清理

5. __declspec(naked) — 完全手動控制

  • 編譯器不生成序言/尾聲代碼 (PUSH EBP; MOV EBP,ESP 等)、不允許 return 返回值。
  • 必須用內聯彙編手寫一切,包括 RET n。
  • 常用於實模式驅動、裸機編程、極致優化熱點路徑。
__declspec(naked) int __stdcall Add(int a, int b) {
    __asm mov eax, a
    __asm add eax, b
    __asm ret 8   ; 必須顯式清理棧
}

三、約定不匹配導致的棧破壞災難

  1. 聲明與定義分離不一致:.h 為 cdecl,.cpp 為 stdcall → Link 階段可能通過 (名字修飾不同導致 LNK2019),若強制轉型調用則運行時棧崩潰。
  2. DLL 導入聲明錯誤:DLL 導出 cdecl,調用端用 WINAPI (stdcall) 定義函數指針 → 參數清理雙方各執一詞,ESP 錯位,後續調用全部崩潰。
// DLL 導出 (cdecl)
__declspec(dllexport) int Func(int a, int b);

// 應用層錯誤聲明 (stdcall)
typedef int (WINAPI *DLLFUNC)(int, int); // WINAPI = __stdcall
DLLFUNC p = (DLLFUNC)GetProcAddress(hLib, "Func");
p(1,2); // 災難發生!

四、總結選型指南

約定參數順序清理者可變參典型用途
cdecl右→左調用者✅C/C++ 默認、CRT 函數、跨模塊通用
stdcall右→左被調用者❌Win32 API、COM、固定參數 DLL
fastcall寄存器+棧被調用者❌高頻調用熱點、編譯器優化
thiscallECX+棧視參數定✅(特殊)C++ 非靜態成員函數
naked完全手動手動手動驅動、Bootloader、極致優化
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved