函數調用約定

函數調用約定

常見的函數調用約定[5]:cdecl,stdcall,fastcall,thiscall,naked call
MFC調用約定(VS6:Project Settings->C/C++ <Category:Code Generation> Calling convention:)

1. __cdecl(C調用約定.The C default calling convention)C/C++ 缺省調用方式

a.壓棧順序:函數參數從右到左.
b.參數棧維護:由調用函數把參數彈出棧,傳送參數的內存棧由調用函數來維護
(正因爲如此,實現可變參數vararg的函數(如printf)只能使用該調用約定).
c.函數修飾名約定:VC將函數編譯後會在函數名前面加上下劃線前綴
d.每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大

2.__stdcall (Pascal方式清理C方式壓棧,通常用於Win32 Api中)

a.壓棧順序:函數參數從右到左的壓棧順序。

b.參數棧維護:被調用函數把參數彈出棧(在退出時清空堆棧)。

c.函數修飾名約定:VC將函數編譯後會在函數名前面加上下劃線前綴,在函數名後加上"@"和參數的字節數。
ex. VC: int f(void *p) (編譯後)-> _f@4(在外部彙編語言裏可以用這個名字引用這個函數)

3.__fastcall (快速調用約定,通過寄存器來傳送參數)

a.壓棧順序:用ECX和EDX傳送前兩個雙字(DWORD)或更小的參數,剩下的 參數仍舊自右向左壓棧傳送
b.參數棧維護:被調用函數在返回前清理傳送參數的內存棧
c.函數修飾名約定:VC將函數編譯後會在函數名前面加上"@"前綴,在函數名後加上"@"和參數的字節數

4. thiscall (本身調用,僅用於“C++”成員函數)

a.壓棧順序:this指針存放於CX/ECX寄存器中,參數從右到左的壓棧順序
b.thiscall不是關鍵詞,因此不能被程序員指定

5. naked call (裸調)

a..當採用1-4的調用約定時,如果必要的話,進入函數時編譯器會產生代碼來
保存ESI,EDI,EBX,EBP寄存器,退出函數時則產生代碼恢復這些寄存器的內容
(這些代碼稱作 prolog and epilog code,一般,ebp,esp的保存是必須的)
b.naked call不產生這樣的代碼。naked call不是類型修飾符,故必須和_declspec共同使用

關鍵字 _stdcall、_cdecl和_fastcall(1-3)可以直接加在要輸出的函數前。
它們對應的命令行參數分別爲/Gz、/Gd和/Gr。缺省狀態爲/Gd,即__cdecl
要完全模仿PASCAL調用約定首先必須使用_stdcall調用約定,函數名修飾約定可通過其它方法模仿
WINAPI宏,Windows.h支持該宏,它可以將出函數翻譯成適當的調用約定,
在WIN32中,它被定義爲_stdcall。使用WINAPI宏可以創建自己的APIs

引用:

很多API函數就是象這樣聲明的:
int WINAPI MessageBoxA(HWND,LPCSTR,LPSTR,UINT);
而WINAPI實際上就是_stdcall.
大多數API都採用_stdcall調用規範,這是因爲幾乎所有的語言都支持_stdcall調用.
相比之下,_cdecl只有在C語言中才能用. 但_cdecl調用有一個特點,就是能夠實現可變參數的函數調用,
比如printf,這用_stdcall調用是不可能的.
_fastcall這種調用規範比較少見,但是在Borland C++ Builder中比較多的採用了這種調用方式.
如果有共享代碼的需要,比如寫DLL,推薦的方法是用_stdcall調用,因爲這樣適用範圍最廣.
如果是C++語言寫的代碼供Delphi這樣的語言調用就必須聲明爲_stdcall,
因爲Pascal不支持cdecl調用(或許Delphi的最新版本能夠支持也說不定,這個不太清楚).
在其他一些地方,比如寫COM組件,幾乎都用的是stdcall調用.
在VC或Delphi或C++Builder裏面都可以從項目設置中更改默認的函數調用規範,
當然也可在函數聲明時加入_stdcall,_cdecl,_fastcall關鍵字來明確的指示本函數用哪種調用約定.
__declspec一般都是用來聲明DLL中的導出函數.這個關鍵字也有一些其他的用法,不過非常罕見.
_declspec主要是用於說明DLL的引出函數的,在某些情況下用_declspec(dllexport)在DLL中聲明引出函數,
比用傳統的DEF文件方便.在普通程序中也可用_declspec(dllimport)說明函數是位於另一個DLL中的導出函數.
以下是在dev-c++裏建立自已的dll時的dll.h裏面的代碼,這裏面有一個:_declspec(dllexport)

#ifndef _DLLH
#define _DLLH//防重複定義

#if BUILDING_DLL
#define DLLIMPORT declspec (dllexport)
#else
#define DLLIMPORT
declspec (dllimport)
#endif
DLLIMPORT void HelloWorld (void);
#endif
上面代碼裏面的_delcspce(dllexport)被定義爲宏,這樣可以提高程序的可讀性.

這個的作用是將函數定義爲導出函數,也就是說這個函數要被包含這個函數的程序之外的程序調用.

本語句中就是:void Helloword(void):

摘自msdn:在 32 位編譯器版本中,可以使用 __declspec(dllexport) 關鍵字從 DLL 導出數據、

函數、類或類成員函數。__declspec(dllexport) 將導出指令添加到對象文件

若要導出函數,__declspec(dllexport) 關鍵字必須出現在調用約定關鍵字的左邊(如果指定了關鍵字)

例如:

_declspec(dllexport) void __cdecl Function1(void);

若要導出類中的所有公共數據成員和成員函數,關鍵字必須出現在類名的左邊,如下所示:

class __declspec(dllexport) CExampleExport : public CObject
{ ... class definition ... };

生成 DLL 時,通常創建一個包含正在導出的函數原型和/或類的頭文件,並將 __declspec(dllexport)

添加到頭文件中的聲明。若要提高代碼的可讀性,請爲 __declspec(dllexport) 定義一個宏並對正在導出的

每個符號使用該宏:#define DllExport __declspec( dllexport )

__declspec(dllexport) 將函數名存儲在 DLL 的導出表中。如果希望優化表的大小

附錄>

1. 修飾名(Decoration name)

  “C”或者“C++”函數在內部(編譯和鏈接)通過修飾名識別。修飾名是編譯器在編譯函數定義或者原

  型時生成的字符串。有些情況下使用函數的修飾名是必要的,如在模塊定義文件裏頭指定輸出“C++”

  重載函數、構造函數、析構函數,又如在彙編代碼裏調用“C””或“C++”函數等。

  1>修飾名由函數名、類名、調用約定、返回類型、參數等共同決定。

  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@@YGXZ”,例如

  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++調用

轉自:https://blog.csdn.net/lioncolumn/article/details/10376891
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章