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

VC++ 函數呼叫慣例深度解析:__cdecl vs __stdcall 組合語言層面差異與實戰驗證

     編輯:VC 教學

前言:呼叫慣例決定參數如何傳遞、誰清理堆疊

C/C++ 預設 __cdecl,Win32 API/回呼函數用 __stdcall (WINAPI/CALLBACK)。兩者在組合語言層面有本質差異。本文以實際反組譯碼驗證參數壓棧順序、堆疊清理責任、名稱修飾規則。

實驗函數原型

void Input(int& m, int& n);  // 參數為引用 (實際傳遞位址)

一、__cdecl (C/C++ 預設)

呼叫端組合語言

lea eax, [ebp-8]   ; 取得 n 位址
push eax
lea ecx, [ebp-4]   ; 取得 m 位址
push ecx
call Input         ; 呼叫函數
add esp, 8         ; 【呼叫者清理堆疊】
  • 參數從右至左壓棧 (先 n 後 m)。
  • 呼叫者負責 add esp, 8 清理堆疊 (2 參數 × 4 位元組)。
  • 名稱修飾:前綴 _ → _Input。
  • 支援可變參數 (呼叫者知參數個數)。

函數內部 (不清理堆疊)

Input:
push ebp
mov ebp, esp
; ... 函數本體 ...
pop ebp
ret                ; 直接返回,無立即數

二、__stdcall (WINAPI / CALLBACK)

呼叫端組合語言

lea eax, [ebp-8]
push eax
lea ecx, [ebp-4]
push ecx
call Input         ; 呼叫函數
; 【無 add esp】呼叫者不清理堆疊

函數內部 (被呼叫者清理)

Input:
push ebp
mov ebp, esp
; ... 函數本體 ...
pop ebp
ret 8              ; 【被呼叫者清理】ret 立即數 = 參數總位元組數
  • 參數同樣從右至左壓棧。
  • 被呼叫者以 ret N 清理堆疊。
  • 名稱修飾:_Func@N (N = 參數總位元組數,十進位)。
  • 不支援可變參數 (被呼叫者編譯時不知參數個數)。

三、參數實際內容驗證

  • __cdecl:[ebp-8] 存 n 位址,[ebp-4] 存 m 位址 (引用傳遞即傳位址)。
  • __stdcall:函數內 [ebp+8] 為第一參數 (m 位址),[ebp+12] 為第二參數 (n 位址),與壓棧順序一致。

四、其他呼叫慣例速查

慣例關鍵字參數傳遞堆疊清理名稱修飾典型用途
__cdecl預設右至左壓棧呼叫者_FuncC/C++ 一般函數、可變參數
__stdcallWINAPI, CALLBACK, PASCAL右至左壓棧被呼叫者_Func@NWin32 API、回呼、COM
__fastcall__fastcall前兩參數 ECX/EDX,其餘右至左被呼叫者@Func@N高效能需求
thiscall隱含 (成員函數)this 在 ECX,參數右至左被呼叫者類別內部非靜態成員函數
naked call__declspec(naked)完全自定自定無手寫組語進出棧

五、實務啟示

  • DLL 匯出函數若給 C/C++ 呼叫,建議明確標註 __cdecl 或 __stdcall 避免不匹配。
  • 函數指標類型必須與實際函數呼叫慣例一致,否則堆疊損壞極難偵錯。
  • WINAPI 宏定義為 __stdcall,自訂 API 應使用此宏保持一致性。
  • VC++ 專案屬性 → C/C++ → 代碼生成 → 呼叫慣例,可設定專案預設 (/Gd=cdecl, /Gz=stdcall, /Gr=fastcall)。

結語

呼叫慣例是 C/C++ 與作業系統、DLL、組語互操作的基石。透過反組譯觀察 push/call/add esp/ret N 細節,能徹底理解參數傳遞與堆疊管理機制,避開跨模組呼叫的隱形陷阱。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved