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調用約定。至此調用約定相關問題已基本說完了,小弟不才,有什么不對的地方還請指正。
