【文檔翻譯】__cdecl/__stdcall/__fastcall?解開神秘的調用約定!

来源:https://www.cnblogs.com/Code-For-What/archive/2023/12/02/17872401.html
-Advertisement-
Play Games

本文檔譯自 www.codeproject.com 的文章 "Calling Conventions Demystified",作者 Nemanja Trifunovic,原文參見此處 引言 - Introduction 在學習 Windows 編程的漫長、艱難而美妙的旅途中,你可能會對函數聲明前出 ...


本文檔譯自 www.codeproject.com 的文章 "Calling Conventions Demystified",作者 Nemanja Trifunovic,原文參見此處


引言 - Introduction

在學習 Windows 編程的漫長、艱難而美妙的旅途中,你可能會對函數聲明前出現的奇怪說明符感到好奇,比如 __cdecl__stdcall__fastcallWINAPI 等等。在閱讀過 MSDN 或其他參考資料之後,你可能知道了這些說明符是用來為函數指定一種叫“調用約定”的東西。在這篇文章中,我會使用 Visual C++ 來向你解釋不同的調用約定。我要強調的是,上面提到的說明符是微軟特有的,如果你想編寫可移植代碼,就不應該使用它們。

那麼,調用約定究竟是什麼呢?當我們調用函數時,通常會將參數傳遞給它,並獲得返回值。而調用約定就描述了參數是如何傳遞、值是如何從函數返回的。它還指定了函數名稱的修飾方式。不過,編寫優秀的 C/C++ 程式真的一定要瞭解調用約定嗎?並不是。但是,它可能有助於調試。此外,如果要把 C/C++ 與彙編代碼鏈接,那麼這也有幫助。

要理解本文,你需要具備彙編編程的一些非常基本的知識。

無論使用哪種調用約定,都會發生以下情況:

  1. 所有參數都被擴展到 4 位元組(除非特別說明,預設在 Win32 上),並放入記憶體的適當位置,這些位置通常在棧上。不過它們也可能被放在寄存器中,這便是通過調用約定指定的。
  2. 程式執行流會跳轉到被調用函數的地址。
  3. 在函數內部,寄存器 ESI、EDI、EBX 和 EBP 的值被保存在棧上。執行這些操作的代碼部分稱為 function prolog,通常由編譯器生成。
  4. 執行函數代碼,並將返回值放入 EAX 寄存器中。
  5. 寄存器 ESI、EDI、EBX 和 EBP 的值從棧中恢復。執行此操作的代碼段稱為 function epilog,與 function prolog 一樣,在大多數情況下,它由編譯器生成。
  6. 參數從棧中移除。此操作稱為清棧(stack cleanup),可以在被調用函數的內部執行,也可以由調用方執行,具體取決於所使用的調用約定。

作為調用約定的例子(不考慮 this),我們將使用一個簡單的函數:

int sumExample (int a, int b)
{
    return a + b;
}

對這個函數的調用看起來像這樣:

int c = sum (2, 3);

對於使用 __cdecl__stdcall__fastcall 的例子,我會把示例代碼編譯成 C 代碼。本文後面提到的函數名修飾用的是 C 的修飾方法。C++ 的名稱修飾方法超出了本文的討論範圍。


C 調用約定 - C calling convention (__cdecl)

這個約定是 C/C++ 的預設調用約定。如果項目被設置成使用其他的調用約定,我們也可以通過顯式聲明 __cdecl 來為某個函數指定:

int __cdecl sumExample (int a, int b);

__cdecl 調用約定的主要特點是:

  1. 參數將從右到左依次壓入棧中。
  2. 由調用者執行清棧。
  3. 函數名用下劃線字元 _ 作為首碼進行修飾。

現在,示例函數的調用看起來像這樣:

; // 參數從右到左依次壓入棧中
push        3    
push        2    

; // 調用函數
call        _sumExample 

; // 增加參數的總大小到 ESP 寄存器(向高位移動棧指針),以此來清理堆棧
add         esp,8 

; // 將 EAX 的返回值複製到局部變數 (int c)
mov         dword ptr [c],eax

被調用函數 sumExample 的內部如下所示:

; // function prolog
  push        ebp  
  mov         ebp,esp 
  sub         esp,0C0h 
  push        ebx  
  push        esi  
  push        edi  
  lea         edi,[ebp-0C0h] 
  mov         ecx,30h 
  mov         eax,0CCCCCCCCh 
  rep stos    dword ptr [edi] 
  
; //    return a + b;
  mov         eax,dword ptr [a] 
  add         eax,dword ptr [b] 

; // function epilog
  pop         edi  
  pop         esi  
  pop         ebx  
  mov         esp,ebp 
  pop         ebp  
  ret



標準調用約定 - Standard calling convention (__stdcall)

這個調用約定常常用在 Win32 API 的函數上。事實上,WINAPI 只是 __stdcall 的另一個名稱。

#define WINAPI __stdcall

同樣,可以為一個函數顯式指定標準調用約定:

int __stdcall sumExample (int a, int b);

我們也可以使用編譯器選項 /Gz 來給所有未顯式聲明約定的函數指定 __stdcall

__stdcall 調用約定的主要特點是:

  1. 參數將從右到左依次壓入棧中。
  2. 由被調用的函數執行清棧。
  3. 函數名通過添加下劃線 _@ 字元和所需的堆棧空間位元組數來修飾。

調用示例如下:

; // 參數從右到左依次壓入棧中
  push        3    
  push        2    
  
; // 調用函數
  call        _sumExample@8

; // 將 EAX 的返回值複製到局部變數 (int c)
  mov         dword ptr [c],eax

函數如下所示:

; // 此處是 function prolog (和 __cdecl 的例子一樣,略過)

; //    return a + b;
  mov         eax,dword ptr [a] 
  add         eax,dword ptr [b] 

; // 此處是 function epilog (和 __cdecl 的例子一樣,略過)

; // 清棧並返回控制流
  ret         8

因為棧由被調用的函數清理,所以通常 __stdcall 調用約定創建的可執行文件比 __cdecl 要小。因為在 __cdecl 中,必須為每個函數調用生成清棧的代碼。另一方面,參數數量可變的函數(如 printf())必須使用 __cdecl,因為只有調用者知道函數調用中的參數數量;所以,也只有調用方纔能執行清棧。


Fast 調用約定 - Fast calling convention (__fastcall)

__fastcall指出,只要有可能,參數就應該放在寄存器中,而不是棧中。這減少了函數調用的成本,因為使用寄存器的操作比使用堆棧的操作要快。

我們可以顯式聲明 __fastcall 來使用約定,如下所示:

int __fastcall sumExample (int a, int b);

我們也可以使用編譯器選項 /Gr 來給所有未顯式聲明約定的函數指定 __fastcall

__fastcall 的主要特點是:

  1. 需要 32 位大小(及以下)的前兩個函數參數被放入寄存器 ECX 和 EDX。其餘的從右向左壓入堆棧。
  2. 被調用的函數負責從堆棧中彈出參數。
  3. 函數名通過在開頭添加 @ 字元並附加 @ 和參數所需的位元組數(十進位)來修飾。

註意:Microsoft 保留在未來的編譯器版本中更改傳遞參數的寄存器的權利。

調用例子如下:

; // 將參數放入寄存器 EDX 和 ECX 中
  mov         edx,3 
  mov         ecx,2 
  
; // 調用函數
  call        @fastcallSum@8
  
; // 從寄存器 EAX 拷貝返回值到局部變數 (int c)  
  mov         dword ptr [c],eax

函數內部:

; // function prolog
  push        ebp  
  mov         ebp,esp 
  sub         esp,0D8h 
  push        ebx  
  push        esi  
  push        edi  
  push        ecx  
  lea         edi,[ebp-0D8h] 
  mov         ecx,36h 
  mov         eax,0CCCCCCCCh 
  rep stos    dword ptr [edi] 
  pop         ecx  
  mov         dword ptr [ebp-14h],edx 
  mov         dword ptr [ebp-8],ecx 
; // return a + b;
  mov         eax,dword ptr [a] 
  add         eax,dword ptr [b] 
;// function epilog  
  pop         edi  
  pop         esi  
  pop         ebx  
  mov         esp,ebp 
  pop         ebp  
  ret

這個調用約定究竟和 __cdecl__stdcall 相比有多快呢?你可以自己尋找答案。通過聲明不同的約定,再比較執行時間看看吧。我沒有發現 __fastcall 比其他調用約定更快,不過你可能會得出不同的結論。


Thiscall - Thiscall

Thiscall 是調用 C++ 類成員函數的預設調用約定(參數數量可變的除外)。

這種約定的主要特點是:

  1. 參數將從右到左依次壓入棧中。this被放在 ECX 寄存器中。
  2. 由被調用的函數執行清棧。

這個調用約定的例子有點不同。首先,代碼被編譯為 C++,而不是 C。其次,我們用一個帶有成員函數的結構體,而不是用自由函數。

struct CSum
{
    int sum ( int a, int b) {return a+b;}
};

函數調用的彙編代碼如下所示:

push        3
push        2
lea         ecx,[sumObj]
call        ?sum@CSum@@QAEHHH@Z            ; CSum::sum
mov         dword ptr [s4],eax

函數內部如下所示:

push        ebp
mov         ebp,esp
sub         esp,0CCh
push        ebx
push        esi
push        edi
push        ecx
lea         edi,[ebp-0CCh]
mov         ecx,33h
mov         eax,0CCCCCCCCh
rep stos    dword ptr [edi]
pop         ecx
mov         dword ptr [ebp-8],ecx
mov         eax,dword ptr [a]
add         eax,dword ptr [b]
pop         edi
pop         esi
pop         ebx
mov         esp,ebp
pop         ebp
ret         8

如果我們有一個成員函數使用可變數量參數會發生什麼?在這種情況下,會使用 __cdeclthis 最後被壓入棧。


總結 - Conclusion

長話短說,我們總結調用約定之間的主要區別:

  • __cdecl 是 C 和 C++ 程式的預設調用約定。這種調用約定的優點是,它允許使用具有可變數量參數的函數。缺點是它會創建更大的可執行文件。
  • __stdcall 多用於 Win32 API 函數。它不允許函數具有可變數量的參數。
  • __fastcall 嘗試將參數放在寄存器中,而不是堆棧中,從而使函數調用更快。
  • Thscall 調用約定是不使用可變參數的 C++ 成員函數使用的預設調用約定。

在大多數情況下,這就是你需要瞭解的關於調用約定的全部內容。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在我們開發一些複雜信息的時候,由於需要動態展示一些相關信息,因此我們需要考慮一些控制項內容的動態展示,可以通過動態構建控制項的方式進行顯示,如動態選項卡展示不同的信息,或者動態展示一個自定義控制項的內容等等,目的就是能夠減少一些硬編碼的處理方式,以及能夠靈活的展示數據。本篇隨筆通過實際案例介紹WPF應用開... ...
  • 痞子衡嵌入式半月刊: 第 86 期 這裡分享嵌入式領域有用有趣的項目/工具以及一些熱點新聞,農曆年分二十四節氣,希望在每個交節之日準時發佈一期。 本期刊是開源項目(GitHub: JayHeng/pzh-mcu-bi-weekly),歡迎提交 issue,投稿或推薦你知道的嵌入式那些事兒。 上期回顧 ...
  • 在src目錄下新建一個文件夾models,用來存放數據模型和操作資料庫的方法。 在models目錄下新建一個文件user.js,用來管理用戶信息相關的資料庫操作。 相關的數據模型和資料庫操作方法,最後通過module.exports暴露出去。 mongoose版本8.0.0 1-創建結構 const ...
  • 隨著移動互聯網的普及,越來越多的人開始學習和欣賞唐詩。不過,對於一些想要獲取指定詩歌ID的人來說,這似乎是一件有點困難的事情。好在《唐詩三百首》介面為我們提供了方便快捷的解決方法。下麵,就讓我們來介紹一下如何獲取指定詩歌ID的《唐詩三百首》介面。 數據源介紹: 數據示例下載 ↓ 《唐詩三百首》共選入 ...
  • 官網 Mongoose.js中文網 (mongoosejs.net) 基本使用 安裝 最新的是mongoose8.0.0版本,基於Promise,以前的版本是基於回調函數。 npm npm i mongoose yarn yarn add mongoose 使用 以mongoose8.0.0舉例: ...
  • 最近有個需求需要實現自定義首頁佈局,需要將屏幕按照 6 列 4 行進行等分成多個格子,然後將組件可拖拽對應格子進行渲染展示。 示例 對比一些已有的插件,發現想要實現產品的交互效果,沒有現成可用的。本身功能並不是太過複雜,於是決定自己基於 vue 手擼一個簡易的 Grid 拖拽佈局。 完整源碼在此,在 ...
  • 項目背景: vue 1.創建 backtop.vue 的回到頂部邏輯的組件 <template> <transition name="back-up-fade"> <div class="back-top" :style="{ bottom: bottom + 'px', right: right ...
  • 理解 async/await 的原理和使用方法是理解現代JavaScript非同步編程的關鍵。這裡我會提供一個詳細的實例,涵蓋原理、流程、使用方法以及一些註意事項。代碼註釋會儘量詳盡,確保你理解每個步驟。 實例:使用async/await進行非同步操作 <!DOCTYPE html> <html lan ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...