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

ATL 布幔之下的秘密 (4):呼叫慣例、堆疊幀、Naked 函數與內聯組語實戰

     編輯:關於VC++

前言:為什麼 ATL 開發者必須懂組語?

ATL 為了極致的效能與體積,大量使用內聯組語、裸函數 (__declspec(naked)) 及自訂呼叫慣例。本文從呼叫慣例、堆疊幀建立、函數前序/尾序代碼、naked 屬性等底層機制切入,層層剖析 ATL 源碼中常見的組語模式。

函數呼叫慣例:__cdecl vs __stdcall

參數傳遞順序:從右至左入堆疊

void fun(int a, int b) {}
// 呼叫 fun(5, 10) 產生組語:
push 10
push 5
call fun

__cdecl (C 呼叫慣例)

  • 呼叫者負責清理堆疊:add esp, 8 (2 參數 × 4 位元組)。
  • 支援可變參數 (如 printf)。
  • 函數名稱修飾:_fun。

__stdcall (標準呼叫慣例 / Pascal 慣例)

  • 被呼叫者負責清理堆疊:ret 8。
  • 不支援可變參數。
  • 函數名稱修飾:_fun@8 (@ 後接參數總位元組數)。
  • Win32 API 多數採用此慣例。

堆疊幀與 EBP/EBP 暫存器

標準函數前序 (Prolog)

push ebp
mov  ebp, esp
sub  esp, N       ; 配置區域變數空間
  • EBP 為基址指標,指向目前堆疊幀底部。
  • 參數位於 [EBP+8], [EBP+12]... (高位址)。
  • 區域變數位於 [EBP-4], [EBP-8]... (低位址)。
  • 返回位址在 [EBP+4],呼叫者 EBP 在 [EBP]。

標準函數尾序 (Epilog)

mov  esp, ebp
pop  ebp
ret  N            ; __stdcall 才有 N,__cdecl 為 ret 0

實戰:直接操作堆疊參數

extern "C" int __stdcall fun(int a, int b) {
    _asm mov dword ptr [ebp+12], 15  // 修改 b 為 15
    _asm mov dword ptr [ebp+8], 14   // 修改 a 為 14
    return a + b;                    // 實際回傳 29
}

驗證呼叫慣例差異:__cdecl 需呼叫者 add esp, 8;__stdcall 由 ret 8 清理。

__declspec(naked) 裸函數:完全掌控

  • 編譯器**不**產生 Prolog/Epilog。
  • 不得定義區域變數 (需手動操作堆疊)。
  • 不得使用 return (需手動 mov eax, ...; ret)。
  • 適用於:Thunk、中斷處理、極致優化的葉子函數。

手寫完整 Naked 函數範例

extern "C" int __declspec(naked) sum(int a, int b) {
    // 手寫 Prolog
    _asm push ebp
    _asm mov  ebp, esp

    // 運算並放入 EAX (返回值慣例)
    _asm mov  eax, dword ptr [ebp + 8]
    _asm add  eax, dword ptr [ebp + 12]

    // 手寫 Epilog
    _asm pop  ebp
    _asm ret  8        // __stdcall 清理 2 參數
}

ATL 實戰應用:_QIThunk 除錯輔助

在 ATLBASE.H 中,_QIThunk 結構用於 _ATL_DEBUG_INTERFACES 追蹤介面查詢與參考計數。其成員函數標記 __declspec(naked),避免額外呼叫開銷,直接在組語層級記錄 QueryInterface、AddRef, Release 呼叫堆疊。

結構對齊與 #pragma pack

Thunk 結構必須無位元組對齊 (#pragma pack(push, 1)),確保機器碼緊密排列,位移計算正確。範例:

struct S { char ch; int i; }; // 預設對齊,大小 8
#pragma pack(push, 1)
struct S1 { char ch; int i; }; // 緊湊,大小 5
#pragma pack(pop)

總結

掌握呼叫慣例、堆疊幀佈局、naked 函數限制與內聯組語,是閱讀、除錯、擴充 ATL 核心程式碼的基礎。下一篇將探討 ATL 的模板元編程技術:策略模式、智慧指標、模擬虛函數等進階樣板應用。

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