__stdcall,__cdecl,__fastcall的區別

微巖發表於2017-09-27

一、三者區別一覽表

__stdcall __cdecl __fastcall
引數傳遞方式 右->左 壓棧 右->左 壓棧
清理棧方式 被呼叫函式清理(即函式自己清理),多資料情況使用這個 呼叫者清理
棧適用場合 Win API c/C++ MFC預設方式可變引數的時候使用
C編譯修飾約定(它們均不改變輸出函式名中的字元大小寫) 約定在輸出函式名前加上一個下劃線字首,後面加上一個“@”符號和其引數的位元組數,格式為_functionname@number 約定僅在輸出函式名前加上一個下劃線字首,格式為_functionname
C++修飾 見下面的”四、名字修飾約定”

二、thiscall

thiscall僅僅應用於”C++”成員函式。this指標存放於CX暫存器,引數從右到左壓。thiscall不是關鍵詞,因此不能被程式設計師指定。
naked call:
採用1-4的呼叫約定時,如果必要的話,進入函式時編譯器會產生程式碼來儲存ESI,EDI,EBX,EBP暫存器,退出函式時則產生程式碼恢復這些暫存器的內容。
naked call不產生這樣的程式碼。naked call不是型別修飾符,故必須和_declspec共同使用。

三、編譯器選項

關鍵字 __stdcall__cdecl__fastcall可以直接加在要輸出的函式前,也可以在編譯環境的Setting…/C/C++ /Code Generation項選擇。當加在輸出函式前的關鍵字與編譯環境中的選擇不同時,直接加在輸出函式前的關鍵字有效。它們對應的命令列引數分別為/Gz、 /Gd和/Gr。預設狀態為/Gd,即__cdecl。

四、名字修飾約定

1、修飾名(Decoration name)

“C” 或者“C++”函式在內部(編譯和連結)通過修飾名識別。修飾名是編譯器在編譯函式定義或者原型時生成的字串。有些情況下使用函式的修飾名是必要的,如在模組定義檔案裡頭指定輸出“C++”過載函式、建構函式、解構函式,又如在彙編程式碼裡呼叫“C””或“C++”函式等。
修飾名由函式名、類名、呼叫約定、返回型別、引數等共同決定。

2、名字修飾約定

隨呼叫約定和編譯種類(C或C++)的不同而變化。函式名修飾約定隨編譯種類和呼叫約定的不同而不同,下面分別說明。

a、C編譯時函式名修飾約定規則:

__stdcall呼叫約定在輸出函式名前加上一個下劃線字首,後面加上一個“@”符號和其引數的位元組數,格式為_functionname@number。
__cdecl呼叫約定僅在輸出函式名前加上一個下劃線字首,格式為_functionname。
__fastcall呼叫約定在輸出函式名前加上一個“@”符號,後面也是一個“@”符號和其引數的位元組數,格式為@functionname@number。
它們均不改變輸出函式名中的字元大小寫,這和PASCAL呼叫約定不同,PASCAL約定輸出的函式名無任何修飾且全部大寫。

b、C++編譯時函式名修飾約定規則:

__stdcall呼叫約定:

  1. 以“?”標識函式名的開始,後跟函式名;
  2. 函式名後面以“@@YG”標識參數列的開始,後跟參數列;
  3. 參數列以代號表示:
    X–void ,
    D–char,
    E–unsigned char,
    F–short,
    H–int,
    I–unsigned int,
    J–long,
    K–unsigned long,
    M–float,
    N–double,
    _N–bool,
    ….
    PA–表示指標,後面的代號表明指標型別,如果相同型別的指標連續出現,以“0”代替,一個“0”代表一次重複;

  4. 參數列的第一項為該函式的返回值型別,其後依次為引數的資料型別,指標標識在其所指資料型別前;

  5. 參數列後以“@Z”標識整個名字的結束,如果該函式無引數,則以“Z”標識結束。
    其格式為“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,例如
    int Test1(char *var1,unsigned long)—–“?Test1@@YGHPADK@Z”
    void Test2() —–“?Test2@@YGXXZ”

__cdecl呼叫約定:

規則同上面的_stdcall呼叫約定,只是參數列的開始標識由上面的“@@YG”變為“@@YA”。

__fastcall呼叫約定:

規則同上面的_stdcall呼叫約定,只是參數列的開始標識由上面的“@@YG”變為“@@YI”。
VC++對函式的省缺宣告是“__cedcl“,將只能被C/C++呼叫.

注意:

  1. _beginthread需要__cdecl的執行緒函式地址,_beginthreadex和CreateThread需要__stdcall的執行緒函式地址。
  2. 一般WIN32的函式都是__stdcall。而且在Windef.h中有如下的定義:
    #define CALLBACK __stdcall
    #define WINAPI  __stdcall
  3. extern “C” _declspec(dllexport) int __cdecl Add(int a, int b);
    typedef int (__cdecl*FunPointer)(int a, int b);
    修飾符的書寫順序如上。
  4. extern “C”的作用:如果Add(int a, int b)是在c語言編譯器編譯,而在c++檔案使用,則需要在c++檔案中宣告:extern “C” Add(int a, int b),因為c編譯器和c++編譯器對函式名的解釋不一樣(c++編譯器解釋函式名的時候要考慮函式引數,這樣是了方便函式過載,而在c語言中不存在函式過載的問題),使用extern “C”,實質就是告訴c++編譯器,該函式是c庫裡面的函式。如果不使用extern “C”則會出現連結錯誤。
    一般象如下使用:

    
    #ifdef _cplusplus
    
    
    #define EXTERN_C extern "C"
    
    
    #else
    
    
    #define EXTERN_C extern
    
    
    #endif
    
    
    #ifdef _cplusplus
    
    extern "C"{
    
    #endif
    
     EXTERN_C int func(int a, int b);
    
    #ifdef _cplusplus
    
    }
    
    #endif
    
  5. MFC提供了一些巨集,可以使用AFX_EXT_CLASS來代替__declspec(DLLexport),並修飾類名,從而匯出類,AFX_API_EXPORT來修飾函式,AFX_DATA_EXPORT來修飾變數
    AFX_CLASS_IMPORT:__declspec(DLLexport)
    AFX_API_IMPORT:__declspec(DLLexport)
    AFX_DATA_IMPORT:__declspec(DLLexport)
    AFX_CLASS_EXPORT:__declspec(DLLexport)
    AFX_API_EXPORT:__declspec(DLLexport)
    AFX_DATA_EXPORT:__declspec(DLLexport)
    AFX_EXT_CLASS:#ifdef _AFXEXT
    AFX_CLASS_EXPORT
    #else
    AFX_CLASS_IMPORT

  6. DLLMain負責初始化(Initialization)和結束 (Termination)工作,每當一個新的程式或者該程式的新的執行緒訪問DLL時,或者訪問DLL的每一個程式或者執行緒不再使用DLL或者結束時,都會呼叫DLLMain。但是,使用TerminateProcess或TerminateThread結束程式或者執行緒,不會呼叫DLLMain。
  7. 一個DLL在記憶體中只有一個例項
    DLL程式和呼叫其輸出函式的程式的關係:
    1)、DLL與程式、執行緒之間的關係
    DLL模組被對映到呼叫它的程式的虛擬地址空間。
    DLL使用的記憶體從呼叫程式的虛擬地址空間分配,只能被該程式的執行緒所訪問。
    DLL的控制程式碼可以被呼叫程式使用;呼叫程式的控制程式碼可以被DLL使用。
    DLLDLL可以有自己的資料段,但沒有自己的堆疊,使用呼叫程式的棧,與呼叫它的應用程式相同的堆疊模式。
    2)、關於共享資料段
    DLL定義的全域性變數可以被呼叫程式訪問;DLL可以訪問呼叫程式的全域性資料。使用同一 DLL的每一個程式都有自己的DLL全域性變數例項。如果多個執行緒併發訪問同一變數,則需要使用同步機制;對一個DLL的變數,如果希望每個使用DLL的執行緒都有自己的值,則應該使用執行緒區域性儲存(TLS,Thread Local Strorage)。

原文連結:
http://www.cnblogs.com/jueyunqi/p/4140141.html

相關文章