回覆列表
  • 1 # 愛達人程式設計達人

    什麼是函式呼叫約定?就是告訴編譯器:怎麼傳遞引數,怎麼傳遞返回值,怎麼平衡堆疊。

    程式碼:

    #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)。

  • 中秋節和大豐收的關聯?
  • 含“春”字的詩句有哪些?