函數的調用約定,當一個函數被調用時,參數的傳遞以及返回值以什麼樣的方式回到調用函數;函數的調用約定就是指參數和返回值是怎麼樣傳遞的,以及是由誰平衡堆棧的。
函數的調用約定主要針對三個問題:
1.函數符號的生成與編譯後的名稱
2.實參的入棧順序
3.形參的開闢與清理方式
一、__cdecl調用約定
cdecl是c標準的調用約定;聲明方式爲:
int function(int a,int b) //不加修飾就是採用默認的c調用約定
int __cdecl function(int a,int b) //明確指出c調度約定
__cdecl約定下的函數符號的生成:int __cdecl Sum(int,int) =》 (?Sum@@YAHHH@Z)
?Sum@@YAHHH@Z
Sum指的是函數名
A指的調用約定 __cdecl
第一個H返回值的類型,後面的H是參數的類型
__cdecl約定下的函數編譯後的名稱:_Sum
具體的函數調用棧的指令,可見上一篇文章;在調用函數中,首先push 14h也就是20,後push 0Ah就是十進制的10,所以c標準調用約定中形參是調用函數中開棧,並且入棧順序是從右向左;從call函數進入被調函數中,返回值由寄存器eax帶出;ebp重新指向(指向棧底寄存器)調用函數的棧底;
00E843E7 add esp,8 //調用函數中恢復棧頂指針esp
由調用函數中的彙編指令可以看出,__cdecl調用約定中,形參是由調用函數清理的;
二、__stdcall
__stdcall是windows標準調用約定,也被稱爲pascal調用約定,因爲使用pascal方式清棧c方式壓棧,在win32應用程序裏,宏APIENTRY,WINAPI,都表示_stdcall;聲明方式爲:
int __stdcall(int a,int b);
__stdcall調用約定下函數符號的生成:int __stdcall Sum(int a, int b) =》?Sum@@YGHHH@Z
?Sum@@YGHHH@Z
Sum指的是函數名
G指的調用約定 __stdcall
第一個H返回值的類型,後面的H是參數的類型
__stdcall調用約定下編譯後的名稱:_Sum@4 @後跟參數類型的字節數
在調用函數的彙編指令中,先push14h將十進制數字20入棧,push 0Ah將十進制數字10入棧,所以在stdcall調用約定下形參是調用函數開闢棧的,形參入棧的順序是從右向左入棧;由call函數跳轉到被調函數,返回值由寄存器帶入調用函數中;注意在被調函數中有:
00A2297A ret 8 //被調函數中
ret指令是對棧頂指針esp進行操作,所以__stdcall調用約定中形參是由被調函數清理的
三、__fastcall
顧名思義,其特點就是快;_fastcall函數調用約定表明了參數應該放在寄存器中,而不是棧中,使用VC編譯器調用約定傳遞參數時,最左邊的兩個不大於4個字節(DEORD)的參數分別放在ecx和edx寄存器,像浮點值還是通過堆棧來傳遞
__fastcall調用約定下函數符號的生成:extern int __fastcall Sum(int , int ,int) =》?Sum@@YIHHHH@Z
?Sum@@YIHHHH@Z
Sum指的是函數名
I指的調用約定 __fastcall
第一個H返回值的類型,後面的H是參數的類型
__fastcall調用約定下編譯後的名稱:@Sum@8
在調用函數中,參數有3個,push 1Eh將十進制的30入棧,將十進制的20與10分別放入edx和ecx寄存器中;所以在fastcall調用約定下,當參數個數不大於兩個,並且參數不大於32字節時,則形參只需要寄存器(edx與ecx)傳遞(也就是不需要開闢棧),如果參數個數大於兩個時,從第三個數起,從右往左進行入棧;在本例中有三個參數,所以第三個參數入棧;然後依舊從call函數跳轉到被調函數中,返回值放在eax寄存器中帶出,在被調函數中有:
00F12985 ret 4 //被調函數中
在被調函數中對esp進行操作,被調函數恢復棧頂指針,因爲只有一個int類型的數值入棧,所以這裏是4
四、__thiscall
thiscall是唯一一個不能明確指明的函數修飾,因爲thiscall只能用於C++類成員函數的調用;由於成員函數調用還有一個this指針,因此必須特殊處理。thiscall意味着:
1.採用棧傳遞參數,參數從右向左入棧。如果參數個數確定,this指針通過ecx傳遞給被調用者;如果參數個數不確定,this指針在所有參數壓棧後被壓人堆棧;
2.對參數個數不確定的,調用者清理堆棧,否則由被調函數清理堆棧