x86 x64下調用約定淺析


 

x86平台下調用約定  

  我們都知道x86平台下常用的有三種調用約定,__cdecl、__stdcall、__fastcall。我們分別對這三種調用約定進行分析。

  __cdecl

  __cdecl是C/C++的默認調用約定,如果不顯示聲明調用約定的情況下,就是該調用約定。下面我們來從匯編層次來熟悉這種調用約定。

我寫了一個函數,如下:

1 int __cdecl TestCdecl(int a, int b, int c, int d, int e)
2 {
3     return a + b + c + d + e;
4 }

  對了,給大家說一下,VS如何進入反匯編。我們在調試界面下,按住Alt+8鍵就可以進入反匯編窗口了。

  可以看出__cdecl參數從右至左入棧,然后由調用者(caller)清理棧區。

  __stdcall

  __stdcall是Windows API默認調用約定,微軟的WINAPI、CALLBACK等宏都是這個調用約定,我還是寫了個例子來看一下。

1 int __stdcall TestStdcall(int a, int b, int c, int d, int e)
2 {
3     return a + b + c + d + e;
4 }

  我們繼續來看一下反匯編。

  可以看到,main函數並沒有對子函數的清棧過程,__stdcall參數跟__cdecl入棧方式相同,從右至左入棧,被調用者(callee)清理棧區。

  __fastcall

  __fastcall是32位下一種特殊的調用約定,我慢還是通過代碼和反匯編看看它到底特殊在哪。

1 int __fastcall TestFastcall(int a, int b, int c, int d, int e)
2 {
3     return a + b + c + d + e;
4 }

  我們可以看到,__fastcall參數還是從右至左入棧,但是不同的是前兩個參數被分別放進了ecx、edx寄存器,如果少於或等於兩個參數,會先將參數放入寄存器。同樣由被調用者(callee)清理棧區。

 x64平台下調用約定

  我們再來看一下64位情況下調用約定。同樣我們測試聲明為__cdecl的調用約定。反匯編結果如下:

  我們看到這個入棧方式怎么跟32位下fastcall的入棧很像。這時候查一下CSDNhttps://www.microsoft.com/china/MSDN/library/Windev/64bit/issuesx64.mspx?pf=true

發現有如下一句話:在設計調用約定時,x64 體系結構利用機會清除了現有 Win32 調用約定(如 __stdcall、__cdecl、__fastcall、_thiscall 等)的混亂。在 Win64 中,只有一個本機調用約定和 __cdecl 之類的修飾符被編譯器忽略。除此之外,減少調用約定行為還為可調試性帶來了好處。

  原來64位平台下只有一種變形的__fastcall的調用約定,前4參數則先放入ecx、edx、r8、r9寄存器,更多的參數放入棧區。這個時候我們要注意的是,在64位下,系統還是為前4個參數預留了棧區空間(每個棧空間大小為8字節,共32字節大小),然后將基存器的值放入所預留的棧區空間。為什么系統要多此一舉呢?我們都知道寄存器傳遞參數速度要遠大於棧區傳值,而將寄存器中的值再放入棧區預留空間,這是為了防止在傳遞參數的過程中,寄存器需要接收其他的值而導致參數無法傳遞,或者其他值無法接收的情況。

  另外我們還看到CSDN上說64位下__fastcall由調用者(caller)清理棧區空間。但是我們為什么沒有看見main()函數清理子函數棧空間的過程呢?

  這是由於64位平台下棧區空間開辟問題導致。我們還在CSDN上看到這樣一句話:與通過 PUSH 和 POP 指令在堆棧中顯式添加和移除參數的 x86 編譯器不同,x64 代碼生成器會預留足夠的堆棧空間,以調用最大目標函數(參數方法)所使用的任何內容。隨后,在調用子函數時,它重復使用相同的堆棧區域來設置參數。

  這句話什么意思呢?它的意思就是我們在64位下一開始系統會為main()函數開辟一個很大的棧區,但是main()函數並未消耗掉這么大的棧區空間,這時候怎么辦呢?子函數就會還繼續利用main()函數的棧區空間,所以main()函數並不用對子函數棧區空間進行清理。

參數列表

  這時候我們再來看一下一些特殊的問題,在C/C++下有一個可變長參數列表的概念。我們來看一下在這種情況下,調用約定又有什么變化。

x86下參數列表

  按照慣例先看代碼

 1 int __fastcall Test(unsigned int n, ...)
 2 {
 3     int sum = 0;
 4     va_list args;
 5     va_start(args, n);
 6     while (n>0)
 7     {
 8         //通過va_arg(args,int)依次獲取參數的值  
 9         sum += va_arg(args, int);
10         n--;
11     }
12     va_end(args);
13     return sum;
14 }

  再來看看反匯編:

  我們可以看到,這時候__fastcall已經退化為__cdecl了,其實其他調用約定也一樣,在32位平台下全部會退化為__cdecl。

x64平台下調用約定

  我們再將函數編譯為64位,反編譯如下:

  這個還是64位下默認的特殊的__fastcall調用約定。至此調用約定相關問題已基本說完了,小弟不才,有什么不對的地方還請指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM