函數調用約定就是描述參數如何傳遞,堆棧由調用方還是被調用方平衡,返回值如何返回等規則。
函數調用約定的幾種類型有:__stdcall, __cdecl, __fastcall, __thiscall, __nakedcall, __pascal
下面介紹幾種常見的函數調用約定(以VS2010編譯器爲例):
(1) __cdecl調用約定
1. 參數從右向左傳遞,放在棧中
2. 棧平衡由調用函數來執行
3. 不定參數的函數可以使用
下面看一個彙編的例子
int a = 1, b = 2;
mov dword ptr [a],1
mov dword ptr [b],2
int sum = Sum(a, b);
mov eax,dword ptr [b] // 參數從右向左壓入棧,壓入參數b
push eax
mov ecx,dword ptr [a] // 參數從右向左壓入棧,壓入參數a
push ecx
call Sum (13611A9h) // 調用函數
add esp,8 // 調用方平衡堆棧(彈出參數)
mov dword ptr [sum],eax // 返回值保存在eax中
int __cdecl Sum(int a, int b)
{
push ebp // 保存上一層函數棧底指針
mov ebp,esp // 設置本層函數棧底指針
sub esp,0C0h // 設置本層函數棧頂指針
push ebx // 保存寄存器的值:ebx、esi、edi
push esi
push edi
lea edi,[ebp-0C0h] // 棧內容賦初值(調試代碼中使用)
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
return a+b;
mov eax,dword ptr [a]
add eax,dword ptr [b] // 將返回值保存在eax中
}
pop edi // 恢復寄存器的值:edi、esi、ebx(相反的順序彈出)
pop esi
pop ebx
mov esp,ebp // 恢復上層函數棧頂指針
pop ebp // 恢復上層函數棧底指針
ret // 沒有棧平衡操作
__cdecl是c/c++默認的調用方式。從上面的彙編代碼可以看到,__cdecl調用方式在函數內沒有任何平衡參數的操作,而在退出函數後對esp執行了加8(add esp,8)的操作。C語言中經常使用的printf函數就是典型的__cdecl調用方式,由於printf的參數可以有多個,所以只能以__cdecl方式調用。
(2)__stdcall調用約定
1. 參數從右向左傳遞,放在棧中
2. 棧平衡操作由被調用函數執行
3. 不定參數的函數無法使用
看一下彙編的例子
int a = 1, b = 2;
mov dword ptr [a],1
mov dword ptr [b],2
int sum = Sum(a, b);
mov eax,dword ptr [b] // 參數從右向左壓入棧,壓入參數b
push eax
mov ecx,dword ptr [a] // 參數從右向左壓入棧,壓入參數a
push ecx
call Sum (13611C2h)
mov dword ptr [sum],eax // 沒有平衡棧操作,平衡操作由函數Sum內部完成
int __stdcall Sum(int a, int b)
{
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
return a+b;
mov eax,dword ptr [a]
add eax,dword ptr [b]
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 8 // 平衡棧操作,棧彈出8個字節,等價於esp += 8
__stdcall與__cdecl除了棧平衡任務由誰來完成以外,其餘部分一樣。(3)__fastcall調用約定
1. 最左邊的兩個不大於4字節的參數分別放在ecx和edx寄存器,其餘參數仍然從右到左壓入棧
2. 被調用方平衡棧
3. 不定參數無法使用
例子:
int a = 1, b = 2, c = 3;
mov dword ptr [a],1
mov dword ptr [b],2
mov dword ptr [c],3
int sum = Sum(a, b, c);
mov eax,dword ptr [c]
push eax
mov edx,dword ptr [b] // 最左邊的第二個參數由edx傳遞
mov ecx,dword ptr [a] // 最左邊的第一個參數由ecx傳遞
call Sum (0E611CCh)
mov dword ptr [sum],eax // 沒有平衡棧操作,平衡棧操作由被調用方完成
int __fastcall Sum(int a, int b, int c)
{
push ebp
mov ebp,esp
sub esp,0D8h
push ebx
push esi
push edi
push ecx
lea edi,[ebp-0D8h]
mov ecx,36h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
pop ecx
mov dword ptr [ebp-14h],edx
mov dword ptr [ebp-8],ecx
return a+b+c;
mov eax,dword ptr [a]
add eax,dword ptr [b]
add eax,dword ptr [c]
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 4 // 平衡棧,4個字節,因爲前兩個參數是通過寄存器傳遞的,只有第三個參數壓入棧中了
但是,對於浮點值、遠指針和__int64類型總是通過棧來傳遞。
(4) __thiscall調用約定
thiscall僅僅用於c++成員函數。this指針存放於ecx寄存器中,參數從右到左壓棧,被調用方平衡棧。thiscall不是關鍵詞不能被程序員指定。
例子:
int a = 1, b = 2;
mov dword ptr [a],1
mov dword ptr [b],2
CTest test;
int sum = test.Sum(a, b);
mov eax,dword ptr [b]
push eax
mov ecx,dword ptr [a]
push ecx
lea ecx,[test] // 對象指針通過ecx傳遞
call CTest::Sum (1911D1h)
mov dword ptr [sum],eax
class CTest
{
public:
int Sum(int a, int b)
{
push ebp
mov ebp,esp
sub esp,0CCh
push ebx
push esi
push edi
push ecx
lea edi,[ebp-0CCh]
mov ecx,33h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
pop ecx
mov dword ptr [ebp-8],ecx
return a+b;
mov eax,dword ptr [a]
add eax,dword ptr [b]
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 8 // 平衡棧