中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C/C++函數的編譯方式與調用約定以及extern “C”的使用

發布時間:2021-10-14 15:37:39 來源:億速云 閱讀:141 作者:柒染 欄目:編程語言

C/C++函數的編譯方式與調用約定以及extern “C”的使用,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

C/C++:函數的編譯方式與調用約定以及extern “C”的使用

函數在C++編譯方式與C編譯方式下的主要不同在于:由于C++引入了函數重載(overload),因此編譯器對同名函數進行了名稱重整(name mangle)。因此,在C++中引

用其他C函數庫時,需要對聲明使用的函數做適當的處理,以告知編譯器做出適應的名稱處理。

函數的調用約定涉及了函數參數的入棧順序、清棧主體(負責清理棧的主體:函數自身還是調用函數者?)、部分名稱重整。

如,在C編譯方式下有_stdcall、_cdecl等調用約定,在C++編譯方式下也有_stdcall、_cedecl等調用約定。

兩個復雜修飾的例子:

extern "C" _declspec(dllexport) int __cdecl Add(int a, int b); //C編譯方式導出_cdecl調用約定函數

typedef int (__cdecl*FunPointer)(int a, int b);


1.編譯方式

c編譯時函數名修飾約定規則:

__stdcall調用約定在輸出函數名前加上一個下劃線前綴,后面加上一個“@”符號和其參數的字節數,格式為_functionname@number。 

__cdecl調用約定僅在輸出函數名前加上一個下劃線前綴,格式為_functionname。 

__fastcall調用約定在輸出函數名前加上一個“@”符號,后面也是一個“@”符號和其參數的字節數,格式@functionname@number。

它們均不改變輸出函數名中的字符大小寫,這和pascal調用約定不同,pascal約定輸出的函數名無任何修飾且全部大寫。 

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-----“?test1@@yghpadk@z” 
void test2-----“?test2@@ygxxz”

__cdecl調用約定:

規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的“@@yg”變為“@@ya”。

__fastcall調用約定:

規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的“@@yg”變為“@@yi”。

2.調用約定

調用約定(Calling Convention)是指在程序設計語言中為了實現函數調用而建立的一種協議。這種協議規定了該語言的函數中的參數傳送方

式、參數是否可變和由誰來處理堆棧等問題。不同的語言定義了不同的調用約定。

在C++中,為了允許操作符重載和函數重載,C++編譯器往往按照某種規則改寫每一個入口點的符號名,以便允許同一個名字(具有不同的參

數類型或者是不同的作用域)有多個用法,而不會打破現有的基于C的鏈接器。這項技術通常被稱為名稱改編(Name Mangling)或者名稱修

飾(Name Decoration)。許多C++編譯器廠商選擇了自己的名稱修飾方案。

因此,為了使其它語言編寫的模塊(如Visual Basic應用程序、Pascal或Fortran的應用程序等)可以調用C/C++編寫的DLL的函數,必須使

用正確的調用約定來導出函數,并且不要讓編譯器對要導出的函數進行任何名稱修飾。

調用約定用來:(一)處理決定函數參數傳送時入棧和(二)出棧的順序(由調用者還是被調用者把參數彈出棧),以及(三)編譯器用來識別函數名

稱的名稱修飾約定等問題。

1、__cdecl

__cdecl是C/C++和MFC程序默認使用的調用約定,也可以在函數聲明時加上__cdecl關鍵字來手工指定。采用__cdecl約定時,函數參數按

照從右到左的順序入棧,并且由調用函數者把參數彈出棧以清理堆棧。因此,實現可變參數的函數只能使用該調用約定。由于每一個使用

__cdecl約定的函數都要包含清理堆棧的代碼,所以產生的可執行文件大小會比較大。__cdecl可以寫成_cdecl。


2、__stdcall

__stdcall調用約定用于調用Win32 API函數。采用__stdcal約定時,函數參數按照從右到左的順序入棧,被調用的函數在返回前清理傳送參

數的棧,函數參數個數固定。由于函數體本身知道傳進來的參數個數,因此被調用的函數可以在返回前用一條ret n指令直接清理傳遞參數的堆

棧。__stdcall可以寫成_stdcall。

3、__fastcall

__fastcall約定用于對性能要求非常高的場合。__fastcall約定將函數的從左邊開始的兩個大小不大于4個字節(DWORD)的參數分別放在

ECX和EDX寄存器,其余的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的堆棧。__fastcall可以寫成_fastcall。


關鍵字__cdecl、__stdcall和__fastcall可以直接加在要輸出的函數前,也可以在編譯環境的Setting...->C/C++->Code Generation項選

擇。它們對應的命令行參數分別為/Gd、/Gz和/Gr。缺省狀態為/Gd,即__cdecl。當加在輸出函數前的關鍵字與編譯環境中的選擇不同時,直

接加在輸出函數前的關鍵字有效。

3._stdcall與_cdecl調用約定對比

在“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函數都是__stdcall類型的,為什么?

首先,我們談一下兩者之間的區別:WINDOWS的函數調用時需要用到棧(STACK,一種先入后出的存儲結構)。當函數調用

完成后,棧需要清除,這里就是問題的關鍵,如何清除?如果我們的函數使用了__cdecl,那么棧的清除工作是由調用者,用

COM的術語來講就是客戶來完成的。這樣帶來了一個棘手的問題,不同的編譯器產生棧的方式不盡相同,那么調用者能否正常

的完成清除工作呢?答案是不能。如果使用__stdcall,上面的問題就解決了,函數自己解決清除工作。所以,在跨(開發)平

臺的調用中,我們都使用__stdcall(雖然有時是以WINAPI的樣子出現)。那么為什么還需要_cdecl呢?當我們遇到這樣的函

數如fprintf()它的參數是可變的,不定長的,被調用者事先無法知道參數的長度,事后的清除工作也無法正常的進行,因此,這

種情況我們只能使用_cdecl。


注意:

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” 的作用(參考:http://hi.baidu.com/qinfengxiaoyue/item/8bd89e81d1cbeb5226ebd9b4)

為什么標準頭文件都有類似以下的結構?

  #ifndef __INCvxWorksh

  #define __INCvxWorksh

  #ifdef __cplusplus

  extern "C" {

  #endif

  /*...*/

  #ifdef __cplusplus

  }

  #endif

  #endif /* __INCvxWorksh */

顯然,頭文件中的編譯宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止該頭文件被重復引用。

那么

#ifdef __cplusplus

extern "C" {

#endif

#ifdef __cplusplus

}

#endif

的作用又是什么呢?

答:被extern "C" 修飾的變量和函數是按照C語言方式編譯和連接的;即為實現C++與C語言的混合編程。

明白了C++中extern "C"的設立動機,我們下面來具體分析extern "C"通常的使用技巧。

extern "C"的慣用法:

(1)在C++中引用C語言中的函數和變量,在包含C語言頭文件(假設為cExample.h)時,需進行下列處理:

extern "C"

{

#include "cExample.h"

}

而在C語言的頭文件中,對其外部函數只能指定為extern類型,C語言中不支持extern "C"聲明,在.c文件中包含了extern "C"時會出現編

譯語法錯誤。

以C++引用C函數例子工程中包含的三個文件的源代碼如下:

  /* c語言頭文件:cExample.h */

  #ifndef C_EXAMPLE_H

  #define C_EXAMPLE_H

  extern int add(int x,int y);

  #endif

  /* c語言實現文件:cExample.c */

  #include "cExample.h"

  int add( int x, int y )

  {

  return x + y;

  }

 


  // c++實現文件,調用add:cppFile.cpp

  extern "C"

  {

  #include "cExample.h"

  }

  int main(int argc, char* argv[])

  {

  add(2,3);

  return 0;

  }

如果C++調用一個C語言編寫的.DLL時,當包括.DLL的頭文件或聲明接口函數時,應加extern "C" { }。

(2)在C中引用C++語言中的函數和變量時,C++的頭文件中的函數聲明需添加前綴extern "C",但是在C語言中不能直接引用

已由extern "C"修飾過的函數聲明或變量的頭文件(因為C編譯方式不支持extern “C” 關鍵字),應該在C中將需要引用的C++

函數的聲明為extern類型。

以C引用C++函數例子工程中包含的三個文件的源代碼如下:

  //C++頭文件 cppExample.h

  #ifndef CPP_EXAMPLE_H

  #define CPP_EXAMPLE_H

  extern "C" int add( int x, int y );

  #endif

  //C++實現文件 cppExample.cpp

  #include "cppExample.h"

  int add( int x, int y )

  {

  return x + y;

  }

 

  /* C實現文件 cFile.c

  /* 但這樣會編譯出錯:#include "cExample.h",因為C編譯不支持extern "C" 關鍵字 */

  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),并修飾類名,從而導出類,

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使用。

DLL可以有自己的數據段,但沒有自己的堆棧,使用調用進程的棧,與調用它的應用程序相同的堆棧模式。

2)、關于共享數據段

DLL定義的全局變量可以被調用進程訪問;DLL可以訪問調用進程的全局數據。使用同一DLL的每一個進程都有自己的DLL全局

變量實例。如果多個線程并發訪問同一變量,則需要使用同步機制;對一個DLL的變量,如果希望每個使用DLL的線程都有自己

的值,則應該使用線程局部存儲(TLS,Thread Local Strorage).


關于C/C++函數的編譯方式與調用約定以及extern “C”的使用問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

福建省| 普陀区| 监利县| 永顺县| 紫云| 大化| 新乐市| 益阳市| 师宗县| 奇台县| 临桂县| 德州市| 乌兰察布市| 甘肃省| 康定县| 崇阳县| 新河县| 乐昌市| 塔河县| 凭祥市| 西乌| 肇东市| 桃园县| 阜新市| 蕲春县| 青海省| 阿荣旗| 新巴尔虎左旗| 浪卡子县| 伊通| 八宿县| 永昌县| 额敏县| 邛崃市| 思南县| 金川县| 盐边县| 广丰县| 梁山县| 西乌珠穆沁旗| 龙口市|