C調用約定__cdecl、__stdcall、__fastcall、__pascal分析

 

參考原文地址:https://www.cnblogs.com/yenyuloong/p/9626658.htmlhtml

 

C/C++ 中不一樣的函數調用規則會生成不一樣的機器代碼,產生不一樣的微觀效果,接下來讓咱們一塊兒來淺析四種調用規則的原理和它們各自的異同。經過一段 C 語言代碼來引導咱們的淺析過程。這裏咱們編寫了三個函數,它們的功能都是返回兩個參數的相加結果,只是每一個函數都有不同的調用規則。編程

用 OllyDBG 查看了編譯後代碼的「真面目」。代碼中有 4 個 CALL,第一個是 printf,咱們不關心這個。後面三個分別是具備 __cdecl,__stdcall,__fastcall 調用規則的函數 CALL(這裏我已經作了註釋)。編程語言

 咱們先介紹 __cdecl 與 __stdcall 調用規則,後面咱們會接着淺析 __fastcall 調用規則。函數

 首先,咱們得明白一個教條(其實也是本身歸納的),那就是 —— 調用規則的區別產生其實就是因爲調用者與被調用者之間的「責任分配」問題。spa

 代碼段中的第 2 個就是 __cdecl 調用規則的 CALL。__cdecl 是 C/C++、MFC 默認的調用規則。咱們能夠看到,在執行 CALL 以前,程序會將參數按照從右到左的方式壓棧,這裏是兩個整型參數,每壓棧一個 ESP 都會減 4,這樣下來 ESP 會減小 8,而後 CALL 這個函數。常規地,咱們能夠看到,這個 CALL 裏面參數的處理和一般狀況下一致,先將 EBP 壓棧保存現場,而後使 EBP 重合於 ESP,再經過 EBP + 偏移地址來取得兩個參數值,賦值再累加到 EAX 中,EAX 將做爲返回值給調用者使用,還原 EBP 現場,調用 RETN 返回到調用者。最後,使得 ESP 加 8。哎!這恰好和開頭對稱嘛!爲了堆棧平衡,ESP 最終又被拉回到了 CALL 以前的位置。咱們暫且能夠小結一下,實際上在 __cdecl 調用規則中,須要調用者來負責清棧操做(由調用者將 ESP 拉高以維持堆棧平衡)。htm

代碼段中的第 3 個是 __stdcall 調用規則的 CALL。__stdcall 調用規則在 Win32 API 函數中用的比較多。跟 __cdecl 同樣,在執行 CALL 以前,程序會先將參數從右到左依次壓棧,咱們跟進 CALL 裏面,能夠看到如下的反彙編代碼,咱們很容易發現,除了最後一條指令,其餘的指令與 __cdecl 調用規則是基本同樣的。最後一條指令是「RETN 0x8」,這是什麼意思呢?實際上呢,就至關於先執行「ADD ESP, 0x8」再執行「POP EIP」 。換言之,就是將 ESP 加 8,而後正常 RETN 返回到調用者。blog

image

   不難發現,__stdcall 調用規則使得被調用者來執行清棧操做(由被調用者函數自身將 ESP 拉高以維持堆棧平衡)這也是 __stdcall 與 __cdecl 調用規則的最根本的區別。內存

   __cdecl 偏向於把責任分配給調用者,動腦筋想一想,咱們的程序在 CALL __cdecl 調用規則的函數以前,把參數從右到左依次壓棧,CALL 返回後,剩下的清棧操做都交給調用者處理,調用者負責拉高 ESP。再回來想一想 __stdcall,在 CALL 中將調用者的 EBP 壓棧以保存現場,而後使 EBP 對齊於 ESP,而後經過 EBP + 偏移地址取得參數,而且通過加法獲得 EAX 返回值,從堆棧彈出 EBP 恢復現場,可是最後不同的地方,程序將執行 「RETN 0x8」 將 ESP 拉回以前的 ESP + 8 的位置,換言之,被調用者將負責清棧操做。這就是以前所謂的「責任分配」的區別。ci

__fastcall 調用規則get

    不難揣測 fastcall 的英文意思貌似是「快速調用」,這一點與它的調用規則息息相關,它的快速是有緣由的,讓咱們繼續來看看以前那張反彙編的截圖,代碼段中的第 4 個就是 __fastcall 調用規則的 CALL。進 CALL 前,出乎意料地,程序將兩個參數從右到左分別傳給了 EDX,ECX 寄存器,講到這裏,學過計算機系統相關知識的人很容易理解爲何這叫「快速調用」了,寄存器比內存快不少不少倍,能夠認爲傳參給寄存器,要比在內存中更快得多,效率更高。

image

因爲參數是直接傳遞給了寄存器,堆棧並未發生改變,在 CALL 中,EBP 壓棧,EBP 和 ESP 對齊以後,ESP 減 8,這個操做有點像對局部變量分配堆棧空間,而後程序將 EDX,ECX 分別賦值給 EBP – 8 與 EBP – 4 這兩個地址,這個過程至關於用寄存器給局部變量賦值,接下來運算結果將保存在 EAX 中,ESP 歸位,EBP 恢復現場,最後 RETN 返回調用者領空。

    本例只傳送了兩個整數型參數。其實呢,對於 __fastcall 調用規則,左邊開始的兩個不大於4字節(int)的參數分別放在ECX和EDX寄存器,其他的參數仍舊自右向左壓棧傳送。而且,__fastcall 調用規則使得被調用者負責清理棧的操做(由被調用者函數自身將 ESP 拉高以維持堆棧平衡),這一點和 __stdcall 同樣。

__pascal 調用規則

    __pascal 是用於 Pascal / Delphi 編程語言的調用規則,C/C++ 中也可使用這種調用規則。簡單地說,__pascal 調用規則與 __stdcall 不一樣的地方就是壓棧順序偏偏相反,前面講到的三種調用規則的壓棧順序都是從右到左依次入棧,__pascal 則是從左到右依次入棧。而且,被調用者(函數自身)將自行完成清棧操做,這和 __stdcall,__fastcall 同樣。因爲比較簡單,我就沒有作出示例。

小結

    作個表格來小結一下,很直觀就能看出這四種調用規則的異同:

調用規則

入棧順序

清棧責任

__cdecl

從右到左

調用者

__stdcall

從右到左

被調用者

__fastcall

從右到左(先 EDX、ECX,再到堆棧)

被調用者

__pascal

從左到右

被調用者

相關文章
相關標籤/搜索