什麼是函式呼叫約定?就是告訴編譯器:怎麼傳遞引數,怎麼傳遞返回值,怎麼平衡堆疊。
程式碼:
#include <stdio.h>
int funAdd(int x,int y)
{
return x+y;
}
int main(void)
int rel = funAdd(1,2);//這裡是堆疊傳參
00401068 push 2 //將2壓入堆疊
0040106A push 1 //將1壓入堆疊
0040106C call @ILT+0(funAdd) (00401005)//呼叫者
00401071 add esp,8 //平衡堆疊(外平棧)
00401074 mov dword ptr [ebp-4],eax
return 0;
引數傳遞:有堆疊傳參、暫存器傳參、堆疊+暫存器傳參這三種方式。
那麼怎麼決定引數傳遞順序的哪?這裡就需要接觸新的概念了。
呼叫約定:
下面列舉了常見的呼叫約定有三種形式:
上面的程式碼中,並沒有用告訴編譯器是哪種呼叫約定,在VC++6.0下預設的呼叫約定是__cdecl,我們來一一驗證下。
引數壓棧順序:從右往左入棧,funAdd(1,2);執行從右往左壓棧順序,先push 2再 push 1,與__cdecl執行引數壓棧順序是一致的。我們接著看平衡堆疊,__cdecl的平衡堆疊要求是:呼叫者清理棧,且是外平棧。我們再來看下我們寫的程式碼, call @ILT+0(funAdd) (00401005), funAdd(0x00401005)是當前的函式,main函式呼叫了 funAdd(0x00401005)函式,所以main函式是呼叫者,接著add esp,8,是外平棧。【在彙編章節已經介紹了,這裡就不在闡述】
以上完全符合__cdecl呼叫約定規則。
那麼我們在程式中加上__cdecl看下是否與上面程式碼引數壓棧順序,平衡堆疊一樣哪?
int __cdecl funAdd(int x,int y) //這樣鍵入__cdecl就可以了
int rel = funAdd(1,2);
00401068 push 2
0040106A push 1
0040106C call @ILT+0(funAdd) (00401005)
00401071 add esp,8
這裡與沒加__cdecl呼叫約定的程式碼執行流程是一樣的。
__stdcall:
int __stdcall funAdd(int x,int y) //這樣鍵入__stdcall就可以了
00401068 push 2//壓入引數
0040106A push 1//壓入引數,壓入順序是從右往左
0040106C call @ILT+10(funAdd) (0040100f)//main函式呼叫funAdd函式(0x0040100f),下面並沒有平衡堆疊,所以這不是__stdcall
00401071 mov dword ptr [ebp-4],eax
1: #include <stdio.h>
2: int __stdcall funAdd(int x,int y)
3: {
4: return x+y;
5: }
0040103E pop edi
0040103F pop esi
00401040 pop ebx
00401041 mov esp,ebp
00401043 pop ebp
00401044 ret 8//內平棧,函式本身自己清理的
總結:
__stdcall呼叫約定:引數從右往左壓入堆疊,且是函式自己本身清理堆疊
__fastcall :
首先看傳入2個引數的:
int__fastcall funAdd(int x,int y) //這樣鍵入__fastcall就可以了
00401068 mov edx,2 //使用暫存器傳參,第一個是edx
0040106D mov ecx,1//使用暫存器傳參,第二個是ecx
00401072 call @ILT+15(funAdd) (00401014)//呼叫funAdd函式,下面並沒有平棧指令,因為並沒有使用堆疊
00401077 mov dword ptr [ebp-4],eax
傳入4個引數:
int __fastcall funAdd(int x,int y,int i,int j)
return x+y+i+j;
0040B510 mov eax,dword ptr [ebp-4]
0040B513 add eax,dword ptr [ebp-8]
0040B516 add eax,dword ptr [ebp+8]
0040B519 add eax,dword ptr [ebp+0Ch]
0040B51C pop edi
0040B51D pop esi
0040B51E pop ebx
0040B51F mov esp,ebp
0040B521 pop ebp
0040B522 ret 8//內平棧
int rel = funAdd(1,2,3,4);
00401068 push 4//壓入堆疊
0040106A push 3//壓入堆疊
0040106C mov edx,2//暫存器傳參
00401071 mov ecx,1//暫存器傳參
00401076 call @ILT+20(funAdd) (00401019)
0040107B mov dword ptr [ebp-4],eax
總結:__fastcall透過暫存器傳參,傳遞引數不破壞堆疊的平衡,所以它不需要平棧,它的執行效率最高。但是當它的引數的數量大於2時,它就會採取堆疊+暫存器的方式傳參,反而會降低執行效率。
C語言中預設採用的呼叫約定是“_cdecl”;_stdcall為WINAPI的呼叫約定,_fastcall為windows核心中常見的呼叫約定。此外還有C++中有this call呼叫約定(_thiscall)。
什麼是函式呼叫約定?就是告訴編譯器:怎麼傳遞引數,怎麼傳遞返回值,怎麼平衡堆疊。
程式碼:
#include <stdio.h>
int funAdd(int x,int y)
{
return x+y;
}
int main(void)
{
int rel = funAdd(1,2);//這裡是堆疊傳參
00401068 push 2 //將2壓入堆疊
0040106A push 1 //將1壓入堆疊
0040106C call @ILT+0(funAdd) (00401005)//呼叫者
00401071 add esp,8 //平衡堆疊(外平棧)
00401074 mov dword ptr [ebp-4],eax
return 0;
}
引數傳遞:有堆疊傳參、暫存器傳參、堆疊+暫存器傳參這三種方式。
那麼怎麼決定引數傳遞順序的哪?這裡就需要接觸新的概念了。
呼叫約定:
下面列舉了常見的呼叫約定有三種形式:
上面的程式碼中,並沒有用告訴編譯器是哪種呼叫約定,在VC++6.0下預設的呼叫約定是__cdecl,我們來一一驗證下。
引數壓棧順序:從右往左入棧,funAdd(1,2);執行從右往左壓棧順序,先push 2再 push 1,與__cdecl執行引數壓棧順序是一致的。我們接著看平衡堆疊,__cdecl的平衡堆疊要求是:呼叫者清理棧,且是外平棧。我們再來看下我們寫的程式碼, call @ILT+0(funAdd) (00401005), funAdd(0x00401005)是當前的函式,main函式呼叫了 funAdd(0x00401005)函式,所以main函式是呼叫者,接著add esp,8,是外平棧。【在彙編章節已經介紹了,這裡就不在闡述】
以上完全符合__cdecl呼叫約定規則。
那麼我們在程式中加上__cdecl看下是否與上面程式碼引數壓棧順序,平衡堆疊一樣哪?
#include <stdio.h>
int __cdecl funAdd(int x,int y) //這樣鍵入__cdecl就可以了
{
return x+y;
}
int main(void)
{
int rel = funAdd(1,2);
00401068 push 2
0040106A push 1
0040106C call @ILT+0(funAdd) (00401005)
00401071 add esp,8
00401074 mov dword ptr [ebp-4],eax
return 0;
}
這裡與沒加__cdecl呼叫約定的程式碼執行流程是一樣的。
__stdcall:
程式碼:
#include <stdio.h>
int __stdcall funAdd(int x,int y) //這樣鍵入__stdcall就可以了
{
return x+y;
}
int main(void)
{
int rel = funAdd(1,2);
00401068 push 2//壓入引數
0040106A push 1//壓入引數,壓入順序是從右往左
0040106C call @ILT+10(funAdd) (0040100f)//main函式呼叫funAdd函式(0x0040100f),下面並沒有平衡堆疊,所以這不是__stdcall
00401071 mov dword ptr [ebp-4],eax
return 0;
}
1: #include <stdio.h>
2: int __stdcall funAdd(int x,int y)
3: {
4: return x+y;
5: }
0040103E pop edi
0040103F pop esi
00401040 pop ebx
00401041 mov esp,ebp
00401043 pop ebp
00401044 ret 8//內平棧,函式本身自己清理的
總結:
__stdcall呼叫約定:引數從右往左壓入堆疊,且是函式自己本身清理堆疊
__fastcall :
首先看傳入2個引數的:
#include <stdio.h>
int__fastcall funAdd(int x,int y) //這樣鍵入__fastcall就可以了
{
return x+y;
}
int main(void)
{
int rel = funAdd(1,2);
00401068 mov edx,2 //使用暫存器傳參,第一個是edx
0040106D mov ecx,1//使用暫存器傳參,第二個是ecx
00401072 call @ILT+15(funAdd) (00401014)//呼叫funAdd函式,下面並沒有平棧指令,因為並沒有使用堆疊
00401077 mov dword ptr [ebp-4],eax
return 0;
}
傳入4個引數:
#include <stdio.h>
int __fastcall funAdd(int x,int y,int i,int j)
{
return x+y+i+j;
0040B510 mov eax,dword ptr [ebp-4]
0040B513 add eax,dword ptr [ebp-8]
0040B516 add eax,dword ptr [ebp+8]
0040B519 add eax,dword ptr [ebp+0Ch]
0040B51C pop edi
0040B51D pop esi
0040B51E pop ebx
0040B51F mov esp,ebp
0040B521 pop ebp
0040B522 ret 8//內平棧
}
int main(void)
{
int rel = funAdd(1,2,3,4);
00401068 push 4//壓入堆疊
0040106A push 3//壓入堆疊
0040106C mov edx,2//暫存器傳參
00401071 mov ecx,1//暫存器傳參
00401076 call @ILT+20(funAdd) (00401019)
0040107B mov dword ptr [ebp-4],eax
return 0;
}
總結:__fastcall透過暫存器傳參,傳遞引數不破壞堆疊的平衡,所以它不需要平棧,它的執行效率最高。但是當它的引數的數量大於2時,它就會採取堆疊+暫存器的方式傳參,反而會降低執行效率。
C語言中預設採用的呼叫約定是“_cdecl”;_stdcall為WINAPI的呼叫約定,_fastcall為windows核心中常見的呼叫約定。此外還有C++中有this call呼叫約定(_thiscall)。