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

C/C++ 函式呼叫慣例完整解析:__cdecl、__stdcall、__fastcall、thiscall 與名稱修飾

     編輯:關於C++

前言:為什麼要懂呼叫慣例?

當 C/C++ 程式與作業系統 API、第三方 DLL、組合語言、不同編譯器產出的模組互動時,呼叫雙方必須對參數傳遞、堆疊清理、名稱修飾達成共識。呼叫慣例違配會導致堆疊損毀、存取違規、連結錯誤等難以診斷的問題。本文系統整理主流呼叫慣例機制、名稱修飾規則與實務應用。

核心呼叫慣例對照表

慣例關鍵字參數壓堆順序堆疊清理者適用場景編譯器命令列名稱修飾 (C 模式)
__cdecl右至左呼叫者C/C++ 預設、可變參數函式/Gd_funcname
__stdcall右至左被呼叫者Win32 API、COM、回呼函式/Gz_funcname@N (N=參數總位元組數)
__fastcall前兩個 DWORD 用 ECX/EDX,其餘右至左壓堆被呼叫者效能關鍵路徑、Borland/Intel 編譯器預設/Gr@funcname@N
thiscallthis 指標放 ECX,參數右至左壓堆被呼叫者C++ 非靜態成員函式 (MSVC)無 (隱含)C++ 名稱修飾 (見下)
naked無序曲 (程式設計師全權負責)程式設計師裸機程式、中斷服務、手寫組語包裝需配合 __declspec(naked)無修飾

詳細機制說明

1. __cdecl (C Declaration) — C/C++ 預設

  • 呼叫者負責清理堆疊 → 支援可變參數 (printf 等),因為只有呼叫者知道實際傳了多少參數。
  • 每個呼叫點都產生清理代碼 → 程式碼體積略大於 __stdcall。
  • MFC 預設慣例。
  • 名稱修飾:僅加前置底線 _FunctionName。

2. __stdcall (Standard Call) — Win32 API 標準

  • 被呼叫者在 RET N 指令中清理堆疊 → 固定參數個數,不支援可變參數。
  • Win32 API、COM 介面方法、回呼函式 (如 WNDPROC) 均採用此慣例。
  • WINAPI 宏定義為 __stdcall,寫可移植程式碼請用 WINAPI。
  • 名稱修飾:_FunctionName@ParamBytes,例如 _MyFunc@12 表示 3 個 32-bit 參數。

3. __fastcall — 寄存器傳參加速

  • Microsoft 實作:前兩個 DWORD (或更小) 整數/指標參數分別放入 ECX, EDX,其餘右至左壓堆。
  • Borland/Embarcadero 實作:最多三個參數用 EAX, EDX, ECX (視型別而定)。
  • 被呼叫者清理堆疊。
  • 名稱修飾:MSVC 為 @FunctionName@ParamBytes;Borland 為 @FunctionName@ParamBytes (類似但細節不同)。
  • 適用於高頻呼叫、葉子函式,但會增加寄存器保存/還原開銷,未必總是更快。

4. thiscall — C++ 成員函式專用

  • this 指標固定放入 ECX (MSVC x86)。
  • 參數右至左壓堆,被呼叫者清理。
  • 非關鍵字,程式設計師無法直接指定,僅供編譯器內部使用。
  • 虛函式透過 vtable 呼叫,實際慣例仍為 thiscall。

5. 裸函式 __declspec(naked)

  • 編譯器不產生序曲/尾聲代碼 (不保存 EBP, EBX, ESI, EDI,不建立堆疊框架)。
  • 函式本體必須用內嵌組語完整實作,包含 RET。
  • 適用於:OS 核心、啟動代碼、極致優化的數學常式、混合語言介面包裝。

C++ 名稱修飾 — 重載與類別的代價

C++ 支援函式重載、命名空間、類別成員,連結器需要唯一符號,因此編碼了完整簽章。

MSVC (Visual C++) 修飾規則摘要

基本格式:?修飾名@@編碼簽章@Z

範例:
int __stdcall Foo(char*, unsigned long) 
→ ?Foo@@YGHPADK@Z
  ??      標識 C++ 修飾名
  Foo     函式名
  @@YG    __stdcall 靜態/全域函式標識
  H       回傳型別 int
  PAD     char* (PA=指標, D=char)
  K       unsigned long
  @Z      結束標記

void __cdecl Bar()
→ ?Bar@@YAXXZ
  @@YA    __cdecl 標識
  X       void 回傳
  X       無參數
  Z       結束

__fastcall 使用 @@YI
成員函式:?MethodName@@QAE... (Q=this 指標, A=非 const, E=const 等)

型別編碼對照 (常見)

代碼型別
Xvoid
Dchar
Eunsigned char
Fshort
Hint
Iunsigned int
Jlong
Kunsigned long
Mfloat
Ndouble
_Nbool
P + 型別代碼指標
R + 型別代碼參考
A + 型別代碼陣列
0重複前一型別 (壓縮)

Borland C++ Builder (BCB) 修飾規則

慣例修飾前綴/後綴參數順序備註
__cdecl (預設)前綴 _,名稱不變右至左壓堆可寫 _cdecl 或 cdecl
__fastcall前綴 @,後綴 @N左至右壓堆 (寄存器優先)BCB 預設優化慣例
__pascal全大寫,無修飾左至右壓堆Pascal 相容
__stdcall名稱不變右至左壓堆Win32 API 相容

實務應用指南

1. 撰寫 DLL 匯出函式

// MyDll.h
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif

// 強制使用 __stdcall,確保跨語言/編譯器相容
extern "C" MYDLL_API int __stdcall Add(int a, int b);

// 定義檔 .def 替代方案 (不需修飾名)
/*
LIBRARY MyDll
EXPORTS
    Add = _Add@8  ; 若用 __stdcall 需指定修飾名
*/

2. 回呼函式型別定義

// Win32 回呼必須用 __stdcall (WINAPI)
typedef BOOL (WINAPI *ENUMWINDOWSPROC)(HWND, LPARAM);

// 錯誤範例:預設 __cdecl 會導致堆疊不平衡
// BOOL CALLBACK MyEnumProc(HWND, LPARAM);  // 缺少 WINAPI/__stdcall

3. 混合呼叫慣例的連結錯誤診斷

  • LNK2001/2019 未解析的外部符號:檢查宣告與定義的呼叫慣例是否一致,extern "C" 是否遺漏。
  • 執行時堆疊損毀:呼叫端與被呼叫端慣例不符 (如一端 __cdecl、一端 __stdcall),啟用 /RTCs (執行時堆疊檢查) 可早期發現。
  • 使用 dumpbin /exports 或 depends.exe 檢視 DLL 實際匯出修飾名,對照標頭檔宣告。

4. 64 位元 (x64) 統一呼叫慣例

MSVC x64 僅支援 **單一呼叫慣例** (類似 __fastcall):

  • 前 4 個整數/指標參數:RCX, RDX, R8, R9
  • 前 4 個浮點參數:XMM0~XMM3
  • 其餘參數:右至左壓堆 (影子空間 32 bytes 保留給被呼叫者)
  • 被呼叫者清理堆疊
  • __cdecl, __stdcall, __fastcall 關鍵字被忽略 (僅相容原始碼)
  • 名稱修飾簡化:無前綴底線,僅 C++ 修飾

總結

  • **跨模組/跨語言介面**:統一使用 __stdcall (或 WINAPI) + extern "C"。
  • **C++ 類別成員**:交給編譯器處理 (thiscall),勿手動干預。
  • **效能熱點**:嘗試 __fastcall 或內聯,但需實測驗證。
  • **除錯神器**:dumpbin /symbols, undname, Depends, /RTCs。
  • **x64 時代**:呼叫慣例簡化,但名稱修飾、ABI 相容仍需注意。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved