函數調用的區別:_cdecl以及_stdcall
一、概念
1)_stdcall調用
_stdcall是Pascal程序的缺省調用方式,參數采用從右到左的壓棧方式,由調用者完成壓棧操作,被調函數自身在返回前清空堆棧。
WIN32 Api都采用_stdcall調用方式,這樣的宏定義說明了問題: #define WINAPI _stdcall
按C編譯方式,_stdcall調用約定在輸出函數名前面加下劃線,后面加“@”符號和參數的字節數,形如_functionname@number.
Therefore, the function declared as
按C++編譯方式,可參看(三)
2)_cdecl調用
_cdecl是C/C++的缺省調用方式,參數采用從右到左的壓棧方式,由調用者完成壓棧操作 ,傳送參數的內存棧由調用者維護。
_cedcl約定的函數只能被C/C++調用,每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大。
按C編譯方式,_cdecl調用約定僅在輸出函數名前面加下劃線,形如_functionname。
按C++編譯方式,可參看(三)
二、區別
幾乎我們寫的每一個WINDOWS API函數都是__stdcall類型的,首先,需要了解兩者之間的區別: WINDOWS的函數調用時需要用到棧(STACK,一種先入后出的存儲結構)。當函數調用完成后,棧需要清除,這里就是問題的關鍵,如何清除?如果我 們的函數使用了_cdecl,那么棧的清除工作是由調用者,用COM的術語來講就是客戶來完成的。這樣帶來了一個棘手的問題,不同的編譯器產生棧的方式不 盡相同,那么調用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的問題就解決了,函數自己解決清除工作。所以,在跨(開發) 平臺的調用中,我們都使用__stdcall(雖然有時是以WINAPI的樣子出現)。那么為什么還需要_cdecl呢?當我們遇到這樣的函數如 fprintf()它的參數是可變的,不定長的,被調用者事先無法知道參數的長度,事后的清除工作也無法正常的進行,因此,這種情況我們只能使用 _cdecl。到這里我們有一個結論,如果你的程序中沒有涉及可變參數,最好使用__stdcall關鍵字。
三、編譯時函數名修飾約定規則
1)首先介紹一下函數修飾名,C” 或者“C++”函數在內部(編譯和鏈接)通過修飾名識別。修飾名是編譯器在編譯函數定義或者原型時生成的字符串。有些情況下使用函數的修飾名是必要的,如 在模塊定義文件里頭指定輸出“C++”重載函數、構造函數、析構函數,又如在匯編代碼里調用“C””或“C++”函數等。修飾名由函數名、類名、調用約定、返回類型、參數等共同決定。名字修飾約定隨調用約定和編譯種類(C或C++)的不同而變化。函數名修飾約定隨編譯種類和調用約定的不同而不同。下面詳細說明在C++編譯器中修飾名的約定方法,在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"。
四、在C++編譯時,引入c庫里面的函數的處理
如果Add(int a, int b)是在c語言編譯器編譯,而在c++文件使用,由(三)知,因為c編譯器和c++編譯器對函數名的解釋不一樣(c++編譯器解釋函數名的時候要考慮函數參數,這樣是了方便函數重載,而在c語言中不存在函數重載的問題),會出現鏈接錯誤。 哪么該如何處理呢?這是關鍵字extern就派上用場了。
而在c++文件使用c編譯器編譯過的函數,則需要在c++文件中聲明:extern "C" Add(int a, int b),使用extern "C",實質就是告訴c++編譯器,該函數是c庫里面的函數。請保持我的名稱,不要給我生成用于鏈接的函數修飾名。
另外順便說一下extern的另一個用途,extern可以置于變量或者函數前,以標示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。#inlcude "***.***" 和 int f() 等價于 extern int f() ;即 extern 申明的函數前不需要加引用頭文件就能用。
參考文獻:
博客:http://www.cnblogs.com/Winston/archive/2008/09/11/1289391.html
1)_stdcall調用
_stdcall是Pascal程序的缺省調用方式,參數采用從右到左的壓棧方式,由調用者完成壓棧操作,被調函數自身在返回前清空堆棧。
WIN32 Api都采用_stdcall調用方式,這樣的宏定義說明了問題: #define WINAPI _stdcall
按C編譯方式,_stdcall調用約定在輸出函數名前面加下劃線,后面加“@”符號和參數的字節數,形如_functionname@number.
Therefore, the function declared as
int func( int a, double b )
is decorated as follows: _func@12
.按C++編譯方式,可參看(三)
2)_cdecl調用
_cdecl是C/C++的缺省調用方式,參數采用從右到左的壓棧方式,由調用者完成壓棧操作 ,傳送參數的內存棧由調用者維護。
_cedcl約定的函數只能被C/C++調用,每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大。
按C編譯方式,_cdecl調用約定僅在輸出函數名前面加下劃線,形如_functionname。
按C++編譯方式,可參看(三)
二、區別
幾乎我們寫的每一個WINDOWS API函數都是__stdcall類型的,首先,需要了解兩者之間的區別: WINDOWS的函數調用時需要用到棧(STACK,一種先入后出的存儲結構)。當函數調用完成后,棧需要清除,這里就是問題的關鍵,如何清除?如果我 們的函數使用了_cdecl,那么棧的清除工作是由調用者,用COM的術語來講就是客戶來完成的。這樣帶來了一個棘手的問題,不同的編譯器產生棧的方式不 盡相同,那么調用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的問題就解決了,函數自己解決清除工作。所以,在跨(開發) 平臺的調用中,我們都使用__stdcall(雖然有時是以WINAPI的樣子出現)。那么為什么還需要_cdecl呢?當我們遇到這樣的函數如 fprintf()它的參數是可變的,不定長的,被調用者事先無法知道參數的長度,事后的清除工作也無法正常的進行,因此,這種情況我們只能使用 _cdecl。到這里我們有一個結論,如果你的程序中沒有涉及可變參數,最好使用__stdcall關鍵字。
三、編譯時函數名修飾約定規則
1)首先介紹一下函數修飾名,C” 或者“C++”函數在內部(編譯和鏈接)通過修飾名識別。修飾名是編譯器在編譯函數定義或者原型時生成的字符串。有些情況下使用函數的修飾名是必要的,如 在模塊定義文件里頭指定輸出“C++”重載函數、構造函數、析構函數,又如在匯編代碼里調用“C””或“C++”函數等。修飾名由函數名、類名、調用約定、返回類型、參數等共同決定。名字修飾約定隨調用約定和編譯種類(C或C++)的不同而變化。函數名修飾約定隨編譯種類和調用約定的不同而不同。下面詳細說明在C++編譯器中修飾名的約定方法,在C編譯器中的變化已經在(一)中闡述。
2)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"。
四、在C++編譯時,引入c庫里面的函數的處理
如果Add(int a, int b)是在c語言編譯器編譯,而在c++文件使用,由(三)知,因為c編譯器和c++編譯器對函數名的解釋不一樣(c++編譯器解釋函數名的時候要考慮函數參數,這樣是了方便函數重載,而在c語言中不存在函數重載的問題),會出現鏈接錯誤。 哪么該如何處理呢?這是關鍵字extern就派上用場了。
而在c++文件使用c編譯器編譯過的函數,則需要在c++文件中聲明:extern "C" Add(int a, int b),使用extern "C",實質就是告訴c++編譯器,該函數是c庫里面的函數。請保持我的名稱,不要給我生成用于鏈接的函數修飾名。
另外順便說一下extern的另一個用途,extern可以置于變量或者函數前,以標示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。#inlcude "***.***" 和 int f() 等價于 extern int f() ;即 extern 申明的函數前不需要加引用頭文件就能用。
參考文獻:
博客:http://www.cnblogs.com/Winston/archive/2008/09/11/1289391.html
posted on 2011-04-19 13:23 Kenny Jiang 閱讀(9922) 評論(12) 編輯 收藏 引用 所屬分類: C++