int function(int a,int b)
調用時只要用result = function(1,2)這樣的方式就可以使用這個函數。但是,當高級語言被編譯成計算機可以識別的機器碼時,有一個問題就凸現出來:在CPU中,計算機沒有辦法知道一個函數調用需要多少個、什麼樣的參數,也沒有硬件可以保存這些參數。也就是說,計算機不知道怎麼給這個函數傳遞參數,傳遞參數的工作必須由函數調用者和函數本身來協調。爲此,計算機提供了一種被稱爲棧的數據結構來支持參數傳遞。
當參數個數多於一個時,按照什麼順序把參數壓入堆棧
函數調用後,由誰來把堆棧恢復原裝
在高級語言中,通過函數調用約定來說明這兩個問題。常見的調用約定有:
cdecl
fastcall
thiscall
naked call
stdcall調用約定
stdcall很多時候被稱爲pascal調用約定,因爲pascal是早期很常見的一種教學用計算機程序設計語言,其語法嚴謹,使用的函數調用約定就是stdcall。在Microsoft C++系列的C/C++編譯器中,常常用PASCAL宏來聲明這個調用約定,類似的宏還有WINAPI和CALLBACK。
int __stdcall function(int a,int b)
stdcall的調用約定意味着:1)參數從右向左壓入堆棧,2)函數自身修改堆棧 3)函數名自動加前導的下劃線,後面緊跟一個@符號,其後緊跟着參數的尺寸
push 1 第一個參數入棧
call function 調用參數,注意此時自動把cs:eip入棧
push ebp 保存ebp寄存器,該寄存器將用來保存堆棧的棧頂指針,可以在函數退出時恢復
mov ebp, esp 保存堆棧指針
mov eax,[ebp + 8H] 堆棧中ebp指向位置之前依次保存有ebp, cs:eip, a, b, ebp +8指向a
add eax,[ebp + 0CH] 堆棧中ebp + 12處保存了b
mov esp, ebp 恢復esp
pop ebp
ret 8
cdecl調用約定
cdecl調用約定又稱爲C調用約定,是C語言缺省的調用約定,它的定義語法是:
int __cdecl function(int a,int b)//明確指出C調用約定
push 1
push 2
call function
add esp, 8 注意:這裏調用者在恢復堆棧
push ebp 保存ebp寄存器,該寄存器將用來保存堆棧的棧頂指針,可以在函數退出時恢復
mov ebp,esp 保存堆棧指針
mov eax,[ebp + 8H] 堆棧中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
add eax,[ebp + 0CH] 堆棧中ebp + 12處保存了b
mov esp,ebp 恢復esp
pop ebp
ret 注意,這裏沒有修改堆棧
int sprintf(char* buffer,const char* format,...)
由於所有的不定參數都可以通過format確定,因此使用不定個數的參數是沒有問題的。
fastcall調用約定和stdcall類似,它意味着:
函數的第一個和第二個DWORD參數(或者尺寸更小的)通過ecx和edx傳遞,其他參數通過從右向左的順序壓棧
被調用函數清理堆棧
函數名修改規則同stdcall
其聲明語法爲:int fastcall function(int a, int b)
thiscall是唯一一個不能明確指明的函數修飾,因爲thiscall不是關鍵字。它是C++類成員函數缺省的調用約定。由於成員函數調用還有一個this指針,因此必須特殊處理,thiscall意味着:
如果參數個數確定,this指針通過ecx傳遞給被調用者;如果參數個數不確定,this指針在所有參數壓棧後被壓入堆棧。對參數個數不定的,調用者清理堆棧,否則函數自己清理堆棧爲了說明這個調用約定,定義如下類和使用代碼:
{
public:
int function1(int a,int b);
int function2(int a,...);
};
{
return a+b;
}
int A::function2(int a,...)
{
va_list ap;
va_start(ap,a);
int i;
int result = 0;
for(i = 0 ; i < a ; i ++)
{
result += va_arg(ap,int);
}
return result;
}
{
A a;
a.function1(1, 2);
a.function2(3, 1, 2, 3);
}
//函數function1調用
00401C1D push 2
00401C1F push 1
00401C21 lea ecx,[ebp-8]
00401C24 call function1 注意,這裏this沒有被入棧
00401C29 push 3
00401C2B push 2
00401C2D push 1
00401C2F push 3
00401C31 lea eax, [ebp-8] 這裏引入this指針
00401C34 push eax
00401C35 call function2
00401C3A add esp, 14h
可見,對於參數個數固定情況下,它類似於stdcall,不定時則類似cdecl
這是一個很少見的調用約定,一般程序設計者建議不要使用。編譯器不會給這種函數增加初始化和清理代碼,更特殊的是,你不能用return返回返回值,只能用插入彙編返回結果。這一般用於實模式驅動程序設計,假設定義一個求和的加法程序,可以定義爲:
{
__asm mov eax,a
__asm add eax,b
__asm ret
}
add eax,[ebp+12]
ret 8
{
__asm mov eax,a
__asm add eax,b
__asm ret 8 //注意後面的8
}
如果定義的約定和使用的約定不一致,則將導致堆棧被破壞,導致嚴重問題,下面是兩種常見的問題:
DLL導入函數時聲明瞭不同的函數約定
以後者爲例,假設我們在dll種聲明瞭一種函數爲:
使用時代碼爲:
hLib = LoadLibrary(...);
result = func(1,2);//導致錯誤