C/C++函數(shù)的編譯方式與調(diào)用約定以及extern “C”的使用,針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
創(chuàng)新互聯(lián)是一家網(wǎng)站設(shè)計、做網(wǎng)站,提供網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,網(wǎng)站制作,建網(wǎng)站,按需策劃設(shè)計,網(wǎng)站開發(fā)公司,公司2013年成立是互聯(lián)行業(yè)建設(shè)者,服務(wù)者。以提升客戶品牌價值為核心業(yè)務(wù),全程參與項目的網(wǎng)站策劃設(shè)計制作,前端開發(fā),后臺程序制作以及后期項目運營并提出專業(yè)建議和思路。
函數(shù)在C++編譯方式與C編譯方式下的主要不同在于:由于C++引入了函數(shù)重載(overload),因此編譯器對同名函數(shù)進行了名稱重整(name mangle)。因此,在C++中引
用其他C函數(shù)庫時,需要對聲明使用的函數(shù)做適當(dāng)?shù)奶幚恚愿嬷幾g器做出適應(yīng)的名稱處理。
函數(shù)的調(diào)用約定涉及了函數(shù)參數(shù)的入棧順序、清棧主體(負(fù)責(zé)清理棧的主體:函數(shù)自身還是調(diào)用函數(shù)者?)、部分名稱重整。
如,在C編譯方式下有_stdcall、_cdecl等調(diào)用約定,在C++編譯方式下也有_stdcall、_cedecl等調(diào)用約定。
兩個復(fù)雜修飾的例子:
extern "C" _declspec(dllexport) int __cdecl Add(int a, int b); //C編譯方式導(dǎo)出_cdecl調(diào)用約定函數(shù)
typedef int (__cdecl*FunPointer)(int a, int b);
1.編譯方式
c編譯時函數(shù)名修飾約定規(guī)則:
__stdcall調(diào)用約定在輸出函數(shù)名前加上一個下劃線前綴,后面加上一個“@”符號和其參數(shù)的字節(jié)數(shù),格式為_functionname@number。
__cdecl調(diào)用約定僅在輸出函數(shù)名前加上一個下劃線前綴,格式為_functionname。
__fastcall調(diào)用約定在輸出函數(shù)名前加上一個“@”符號,后面也是一個“@”符號和其參數(shù)的字節(jié)數(shù),格式@functionname@number。
它們均不改變輸出函數(shù)名中的字符大小寫,這和pascal調(diào)用約定不同,pascal約定輸出的函數(shù)名無任何修飾且全部大寫。
c++編譯時函數(shù)名修飾約定規(guī)則:
__stdcall調(diào)用約定:
1、以“?”標(biāo)識函數(shù)名的開始,后跟函數(shù)名;
2、函數(shù)名后面以“@@yg”標(biāo)識參數(shù)表的開始,后跟參數(shù)表;
3、參數(shù)表以代號表示:
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--表示指針,后面的代號表明指針類型,如果相同類型的指針連續(xù)出現(xiàn),以“0”代替,一個“0”代表一次重復(fù);
4、參數(shù)表的第一項為該函數(shù)的返回值類型,其后依次為參數(shù)的數(shù)據(jù)類型,指針標(biāo)識在其所指數(shù)據(jù)類型前;
5、參數(shù)表后以“@z”標(biāo)識整個名字的結(jié)束,如果該函數(shù)無參數(shù),則以“z”標(biāo)識結(jié)束。
其格式為“?functionname@@yg*****@z”或“?functionname@@yg*xz”,例如
int test1-----“?test1@@yghpadk@z”
void test2-----“?test2@@ygxxz”
__cdecl調(diào)用約定:
規(guī)則同上面的_stdcall調(diào)用約定,只是參數(shù)表的開始標(biāo)識由上面的“@@yg”變?yōu)椤癅@ya”。
__fastcall調(diào)用約定:
規(guī)則同上面的_stdcall調(diào)用約定,只是參數(shù)表的開始標(biāo)識由上面的“@@yg”變?yōu)椤癅@yi”。
2.調(diào)用約定
調(diào)用約定(Calling Convention)是指在程序設(shè)計語言中為了實現(xiàn)函數(shù)調(diào)用而建立的一種協(xié)議。這種協(xié)議規(guī)定了該語言的函數(shù)中的參數(shù)傳送方
式、參數(shù)是否可變和由誰來處理堆棧等問題。不同的語言定義了不同的調(diào)用約定。
在C++中,為了允許操作符重載和函數(shù)重載,C++編譯器往往按照某種規(guī)則改寫每一個入口點的符號名,以便允許同一個名字(具有不同的參
數(shù)類型或者是不同的作用域)有多個用法,而不會打破現(xiàn)有的基于C的鏈接器。這項技術(shù)通常被稱為名稱改編(Name Mangling)或者名稱修
飾(Name Decoration)。許多C++編譯器廠商選擇了自己的名稱修飾方案。
因此,為了使其它語言編寫的模塊(如Visual Basic應(yīng)用程序、Pascal或Fortran的應(yīng)用程序等)可以調(diào)用C/C++編寫的DLL的函數(shù),必須使
用正確的調(diào)用約定來導(dǎo)出函數(shù),并且不要讓編譯器對要導(dǎo)出的函數(shù)進行任何名稱修飾。
調(diào)用約定用來:(一)處理決定函數(shù)參數(shù)傳送時入棧和(二)出棧的順序(由調(diào)用者還是被調(diào)用者把參數(shù)彈出棧),以及(三)編譯器用來識別函數(shù)名
稱的名稱修飾約定等問題。
1、__cdecl
__cdecl是C/C++和MFC程序默認(rèn)使用的調(diào)用約定,也可以在函數(shù)聲明時加上__cdecl關(guān)鍵字來手工指定。采用__cdecl約定時,函數(shù)參數(shù)按
照從右到左的順序入棧,并且由調(diào)用函數(shù)者把參數(shù)彈出棧以清理堆棧。因此,實現(xiàn)可變參數(shù)的函數(shù)只能使用該調(diào)用約定。由于每一個使用
__cdecl約定的函數(shù)都要包含清理堆棧的代碼,所以產(chǎn)生的可執(zhí)行文件大小會比較大。__cdecl可以寫成_cdecl。
2、__stdcall
__stdcall調(diào)用約定用于調(diào)用Win32 API函數(shù)。采用__stdcal約定時,函數(shù)參數(shù)按照從右到左的順序入棧,被調(diào)用的函數(shù)在返回前清理傳送參
數(shù)的棧,函數(shù)參數(shù)個數(shù)固定。由于函數(shù)體本身知道傳進來的參數(shù)個數(shù),因此被調(diào)用的函數(shù)可以在返回前用一條ret n指令直接清理傳遞參數(shù)的堆
棧。__stdcall可以寫成_stdcall。
3、__fastcall
__fastcall約定用于對性能要求非常高的場合。__fastcall約定將函數(shù)的從左邊開始的兩個大小不大于4個字節(jié)(DWORD)的參數(shù)分別放在
ECX和EDX寄存器,其余的參數(shù)仍舊自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的堆棧。__fastcall可以寫成_fastcall。
關(guān)鍵字__cdecl、__stdcall和__fastcall可以直接加在要輸出的函數(shù)前,也可以在編譯環(huán)境的Setting...->C/C++->Code Generation項選
擇。它們對應(yīng)的命令行參數(shù)分別為/Gd、/Gz和/Gr。缺省狀態(tài)為/Gd,即__cdecl。當(dāng)加在輸出函數(shù)前的關(guān)鍵字與編譯環(huán)境中的選擇不同時,直
接加在輸出函數(shù)前的關(guān)鍵字有效。
3._stdcall與_cdecl調(diào)用約定對比
在“windef.h”頭文件中可找到:
#define CALLBACK __stdcall#define WINAPI __stdcall#define WINAPIV __cdecl#define APIENTRY WINAPI#define APIPRIVATE __stdcall#define PASCAL __stdcall#define cdecl _cdecl#ifndef CDECL#define CDECL _cdecl#endif幾乎我們寫的每一個WINDOWS API函數(shù)都是__stdcall類型的,為什么?
首先,我們談一下兩者之間的區(qū)別:WINDOWS的函數(shù)調(diào)用時需要用到棧(STACK,一種先入后出的存儲結(jié)構(gòu))。當(dāng)函數(shù)調(diào)用
完成后,棧需要清除,這里就是問題的關(guān)鍵,如何清除?如果我們的函數(shù)使用了__cdecl,那么棧的清除工作是由調(diào)用者,用
COM的術(shù)語來講就是客戶來完成的。這樣帶來了一個棘手的問題,不同的編譯器產(chǎn)生棧的方式不盡相同,那么調(diào)用者能否正常
的完成清除工作呢?答案是不能。如果使用__stdcall,上面的問題就解決了,函數(shù)自己解決清除工作。所以,在跨(開發(fā))平
臺的調(diào)用中,我們都使用__stdcall(雖然有時是以WINAPI的樣子出現(xiàn))。那么為什么還需要_cdecl呢?當(dāng)我們遇到這樣的函
數(shù)如fprintf()它的參數(shù)是可變的,不定長的,被調(diào)用者事先無法知道參數(shù)的長度,事后的清除工作也無法正常的進行,因此,這
種情況我們只能使用_cdecl。
注意:
1、_beginthread需要__cdecl的線程函數(shù)地址,_beginthreadex和CreateThread需要__stdcall的線程函數(shù)地址。
2、一般WIN32的函數(shù)都是__stdcall。而且在Windef.h中有如下的定義:
#define CALLBACK __stdcall
#define WINAPI __stdcall
3、復(fù)雜函數(shù)聲明或指針的修飾符示例:
extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);
typedef int (__cdecl*FunPointer)(int a, int b);
4、extern ”C” 的作用(參考:http://hi.baidu.com/qinfengxiaoyue/item/8bd89e81d1cbeb5226ebd9b4)
為什么標(biāo)準(zhǔn)頭文件都有類似以下的結(jié)構(gòu)?
#ifndef __INCvxWorksh#define __INCvxWorksh#ifdef __cplusplusextern "C" {#endif/*...*/#ifdef __cplusplus}#endif#endif /* __INCvxWorksh */顯然,頭文件中的編譯宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止該頭文件被重復(fù)引用。
那么
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
的作用又是什么呢?
答:被extern "C" 修飾的變量和函數(shù)是按照C語言方式編譯和連接的;即為實現(xiàn)C++與C語言的混合編程。
明白了C++中extern "C"的設(shè)立動機,我們下面來具體分析extern "C"通常的使用技巧。
extern "C"的慣用法:
(1)在C++中引用C語言中的函數(shù)和變量,在包含C語言頭文件(假設(shè)為cExample.h)時,需進行下列處理:
extern "C"
{
#include "cExample.h"
}
而在C語言的頭文件中,對其外部函數(shù)只能指定為extern類型,C語言中不支持extern "C"聲明,在.c文件中包含了extern "C"時會出現(xiàn)編
譯語法錯誤。
以C++引用C函數(shù)例子工程中包含的三個文件的源代碼如下:
/* c語言頭文件:cExample.h */#ifndef C_EXAMPLE_H#define C_EXAMPLE_Hextern int add(int x,int y);#endif/* c語言實現(xiàn)文件:cExample.c */#include "cExample.h"int add( int x, int y ){return x + y;}
// c++實現(xiàn)文件,調(diào)用add:cppFile.cppextern "C"{#include "cExample.h"}int main(int argc, char* argv[]){add(2,3);return 0;}如果C++調(diào)用一個C語言編寫的.DLL時,當(dāng)包括.DLL的頭文件或聲明接口函數(shù)時,應(yīng)加extern "C" { }。
(2)在C中引用C++語言中的函數(shù)和變量時,C++的頭文件中的函數(shù)聲明需添加前綴extern "C",但是在C語言中不能直接引用
已由extern "C"修飾過的函數(shù)聲明或變量的頭文件(因為C編譯方式不支持extern “C” 關(guān)鍵字),應(yīng)該在C中將需要引用的C++
中函數(shù)的聲明為extern類型。
以C引用C++函數(shù)例子工程中包含的三個文件的源代碼如下:
//C++頭文件 cppExample.h#ifndef CPP_EXAMPLE_H#define CPP_EXAMPLE_Hextern "C" int add( int x, int y );#endif//C++實現(xiàn)文件 cppExample.cpp#include "cppExample.h"int add( int x, int y ){return x + y;}
/* C實現(xiàn)文件 cFile.c/* 但這樣會編譯出錯:#include "cExample.h",因為C編譯不支持extern "C" 關(guān)鍵字 */extern int add( int x, int y );int main( int argc, char* argv[] ){add( 2, 3 );return 0;}5、MFC提供了一些宏,可以使用AFX_EXT_CLASS來代替__declspec(DLLexport),并修飾類名,從而導(dǎo)出類,
AFX_API_EXPORT來修飾函數(shù),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負(fù)責(zé)初始化(Initialization)和結(jié)束(Termination)工作,每當(dāng)一個新的進程或者該進程的新的線程訪問DLL時,或
者訪問DLL的每一個進程或者線程不再使用DLL或者結(jié)束時,都會調(diào)用DLLMain。但是,使用TerminateProcess或
TerminateThread結(jié)束進程或者線程,不會調(diào)用DLLMain。
7、一個DLL在內(nèi)存中只有一個實例
DLL程序和調(diào)用其輸出函數(shù)的程序的關(guān)系:
1)、DLL與進程、線程之間的關(guān)系
DLL模塊被映射到調(diào)用它的進程的虛擬地址空間。
DLL使用的內(nèi)存從調(diào)用進程的虛擬地址空間分配,只能被該進程的線程所訪問。
DLL的句柄可以被調(diào)用進程使用;調(diào)用進程的句柄可以被DLL使用。
DLL可以有自己的數(shù)據(jù)段,但沒有自己的堆棧,使用調(diào)用進程的棧,與調(diào)用它的應(yīng)用程序相同的堆棧模式。
2)、關(guān)于共享數(shù)據(jù)段
DLL定義的全局變量可以被調(diào)用進程訪問;DLL可以訪問調(diào)用進程的全局?jǐn)?shù)據(jù)。使用同一DLL的每一個進程都有自己的DLL全局
變量實例。如果多個線程并發(fā)訪問同一變量,則需要使用同步機制;對一個DLL的變量,如果希望每個使用DLL的線程都有自己
的值,則應(yīng)該使用線程局部存儲(TLS,Thread Local Strorage).
關(guān)于C/C++函數(shù)的編譯方式與調(diào)用約定以及extern “C”的使用問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識。
網(wǎng)頁名稱:C/C++函數(shù)的編譯方式與調(diào)用約定以及extern“C”的使用
網(wǎng)站網(wǎng)址:http://m.newbst.com/article22/gcepcc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、網(wǎng)站建設(shè)、響應(yīng)式網(wǎng)站、網(wǎng)站營銷、軟件開發(fā)、App設(shè)計
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)