[簡介][]
<h2>目錄</h2>
<ul>
<li><a href="#orgheadline19">1. 關(guān)于 <code>C++</code> 中的 extern "C"</a>
<ul>
<li><a href="#orgheadline1">1.1. 簡介</a></li>
<li><a href="#orgheadline4">1.2. 問題的引出</a>
<ul>
<li><a href="#orgheadline2">1.2.1. 某企業(yè)曾經(jīng)給出如下的一道面試題</a></li>
<li><a href="#orgheadline3">1.2.2. 問題分析</a></li>
</ul>
</li>
<li><a href="#orgheadline10">1.3. 關(guān)于 extern "C"</a>
<ul>
<li><a href="#orgheadline5">1.3.1. 被 extern "C" 限定的函數(shù)或變量是 <code>extern</code> 類型的。</a></li>
<li><a href="#orgheadline9">1.3.2. 被 extern "C" 修飾的變量和函數(shù)是按照 <code>C</code> 語言方式編譯和連接的锻煌。</a>
<ul>
<li><a href="#orgheadline6">1.3.2.1. 首先看看 <code>C++</code> 中氧猬,在未加 extern "C" 聲明時尺棋,對類似 <code>C</code> 的函數(shù)是怎樣編譯的红竭。</a></li>
<li><a href="#orgheadline7">1.3.2.2. 其次涵卵,看看在未加 extern "C" 聲明時佣谐,是如何連接的提佣。</a></li>
<li><a href="#orgheadline8">1.3.2.3. 再者吮蛹,看看加 extern "C" 聲明后的編譯和連接方式</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#orgheadline14">1.4. 用法舉例</a>
<ul>
<li><a href="#orgheadline11">1.4.1. <code>C++</code> 引用 <code>C</code> 函數(shù)的具體例子</a></li>
<li><a href="#orgheadline12">1.4.2. <code>C</code> 引用 <code>C++</code> 函數(shù)的具體例子</a></li>
<li><a href="#orgheadline13">1.4.3. 對 <code>__BEGIN_DECLS</code> 和 <code>__END_DECLS</code> 的理解</a></li>
</ul>
</li>
<li><a href="#orgheadline18">1.5. 總結(jié)</a>
<ul>
<li><a href="#orgheadline15">1.5.1. extern "C" 只是 <code>C++</code> 的關(guān)鍵字,不是 <code>C</code> 的</a></li>
<li><a href="#orgheadline16">1.5.2. 被 extern "C" 修飾的目標(biāo)一般是對一個C或者 <code>C++</code> 函數(shù)的聲明</a></li>
<li><a href="#orgheadline17">1.5.3. extern "C" 這個關(guān)鍵字聲明的真實目的拌屏,就是實現(xiàn) <code>C++</code> 與C及其它語言的混合編程</a></li>
</ul>
</li>
</ul>
</li>
</ul>
關(guān)于 C++
中的 extern "C"<a id="orgheadline19"></a>
簡介 {#orgheadline1}
C++
語言的創(chuàng)建初衷是 "a better C"潮针,但是這并不意味著 C++
中類似 C
語言的全局變量和函數(shù)所采用的編譯和連接方式與 C
語言完全相同。作為一種欲與 C
兼容的語言倚喂, C++
保留了一部分過程式語言的特點(被世人稱為"不徹底地面向?qū)ο?)每篷,因而它可以定義不屬于任何類的全局變量和函數(shù)。但是端圈, C++
畢竟是一種面向?qū)ο蟮某绦蛟O(shè)計語言焦读,為了支持函數(shù)的重載, C++
對全局函數(shù)的處理方式與 C
有明顯的不同舱权。
本文將介紹 C++
中如何通過 extern "C" 關(guān)鍵字支持 C
語言矗晃。
{#問題的引出}<a id="orgheadline4"></a>
某企業(yè)曾經(jīng)給出如下的一道面試題<a id="orgheadline2"></a>
為什么標(biāo)準(zhǔn)頭文件都有類似以下的結(jié)構(gòu)?
//incvxworks.h
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
問題分析<a id="orgheadline3"></a>
對于上面問題宴倍,顯然张症,頭文件中的編譯宏 #ifndef __INCvxWorksh
、 #define __INCvxWorksh
鸵贬、 #endif
的作用是防止該頭文件被重復(fù)引用俗他。
那么,
#ifdef __cplusplus
extern "C" {
#endif
和
#ifdef __cplusplus
}
#endif
的作用又是什么呢阔逼?我們將在后面對此進行詳細(xì)說明拯辙。
關(guān)于 extern "C"<a id="orgheadline10"></a>
前面的題目中的 __cplusplus
宏,是用來識別編譯器的颜价,也就是說涯保,將當(dāng)前代碼編譯的時候,是否將代碼作為 C++
進行編譯周伦。如果是夕春,則定義了 __cplusplus
宏。更多內(nèi)容专挪,這里就不詳細(xì)說明了及志。
而題目中的 extern "C" 包含雙重含義片排,從字面上即可得到:首先,被它修飾的目標(biāo)是 extern
的速侈;其次率寡,被它修飾的目標(biāo)是 C
的。
具體如下:
被 extern "C" 限定的函數(shù)或變量是 extern
類型的倚搬。<a id="orgheadline5"></a>
extern
是 C/C++
語言中表明函數(shù)和全局變量作用范圍(可見性)的關(guān)鍵字冶共,該關(guān)鍵字告訴編譯器,其聲明的函數(shù)和變量可以在本模塊或其它模塊中使用每界。
注意捅僵,語句 extern int a;
僅僅是對變量的聲明,其并不是在定義變量 a
眨层,聲明變量并未為 a
分配內(nèi)存空間庙楚。定義語句形式為 int a;
,變量 a
在所有模塊中作為一種全局變量只能被定義一次趴樱,否則會出現(xiàn)連接錯誤馒闷。
在引用全局變量和函數(shù)之前,必須要有這個變量或者函數(shù)的聲明(或者定義)叁征。通常窜司,在模塊的頭文件中對本模塊提供給其它模塊引用的函數(shù)和全局變量以關(guān)鍵字 extern
聲明。例如航揉,如果模塊 B
欲引用該模塊 A
中定義的全局變量和函數(shù)時只需包含模塊 A
的頭文件即可塞祈。這樣,模塊B中調(diào)用模塊 A
中的函數(shù)時帅涂,在編譯階段议薪,模塊 B
雖然找不到該函數(shù),但是并不會報錯媳友;它會在連接階段中從模塊 A
編譯生成的目標(biāo)代碼中找到此函數(shù)斯议。
與 extern
對應(yīng)的關(guān)鍵字是 static
,被它修飾的全局變量和函數(shù)只能在本模塊中使用醇锚。因此哼御,一個函數(shù)或變量只可能被本模塊使用時,其不可能被 extern "C" 修飾焊唬。
被 extern "C" 修飾的變量和函數(shù)是按照 C
語言方式編譯和連接的恋昼。<a id="orgheadline9"></a>
首先看看 C++
中,在未加 extern "C" 聲明時赶促,對類似 C
的函數(shù)是怎樣編譯的液肌。<a id="orgheadline6"></a>
作為一種面向?qū)ο蟮恼Z言, C++
支持函數(shù)重載鸥滨,而過程式語言 C
則不支持嗦哆。所以谤祖,函數(shù)被 C++
編譯后在符號庫中的名字與 C
語言的有所不同。例如老速,假設(shè)某個函數(shù)的原型為:
void foo( int x, int y );
該函數(shù)被 C
編譯器編譯后在符號庫中的名字為 _foo
粥喜,而 C++
編譯器則會產(chǎn)生像 _foo_int_int
之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機制橘券,生成的新名字稱為 mangled name
)良姆。 _foo_int_int
這樣的名字包含了函數(shù)名浦译、函數(shù)參數(shù)數(shù)量及類型信息木人, C++
就是靠這種機制來實現(xiàn)函數(shù)重載的流昏。例如但两,在 C++
中鬓梅,函數(shù) void foo( int x, int y )
與 void foo( int x, float y )
編譯生成的符號是不相同的,后者為 _foo_int_float
谨湘。
同樣地绽快, C++
中的變量除支持局部變量外,還支持類成員變量和全局變量紧阔。用戶所編寫程序的類成員變量可能與全局變量同名坊罢,我們以 .
來區(qū)分。而本質(zhì)上擅耽,編譯器在進行編譯時活孩,與函數(shù)的處理相似,也為類中的變量取了一個獨一無二的名字乖仇,這個名字與用戶程序中同名的全局變量名字不同憾儒。
其次,看看在未加 extern "C" 聲明時乃沙,是如何連接的起趾。<a id="orgheadline7"></a>
假設(shè)在 C++
中,模塊 A
的頭文件如下:
//模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
在模塊 B
中引用該函數(shù):
// 模塊B實現(xiàn)文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);
實際上警儒,在連接階段训裆,連接器會從模塊 A
生成的目標(biāo)文件 moduleA.obj
中尋找 _foo_int_int
這樣的符號!
對于上面例子蜀铲,如果 B
模塊是 C
程序边琉,而A模塊是 C++
庫頭文件的話,會導(dǎo)致鏈接錯誤记劝;同理艺骂,如果B模塊是 C++
程序,而A模塊是C庫的頭文件也會導(dǎo)致錯誤隆夯。
再者钳恕,看看加 extern "C" 聲明后的編譯和連接方式<a id="orgheadline8"></a>
加 extern "C" 聲明后别伏,模塊 A
的頭文件變?yōu)椋?/p>
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
在模塊 B
的實現(xiàn)文件中仍然調(diào)用 foo( 2,3 )
,其結(jié)果忧额,將會是 C
語言的編譯連接方式:模塊 A
編譯生成 foo
的目標(biāo)代碼時厘肮,沒有對其名字進行特殊處理,采用了 C
語言的方式睦番;連接器在為模塊 B
的目標(biāo)代碼尋找 foo(2,3)
調(diào)用時类茂,尋找的是未經(jīng)修改的符號名 _foo
。
如果在模塊 A
中函數(shù)聲明了 foo
為 extern "C" 類型托嚣,而模塊 B
中包含的是 extern int foo( int x, int y )
巩检,則模塊 B
找不到模塊 A
中的函數(shù)(因為這樣的聲明沒有使用 extern "C" 指明采用C語言的編譯鏈接方式);反之亦然示启。
所以兢哭,綜上可知, extern "C" 這個聲明的真實目的夫嗓,就是實現(xiàn) C++
與 C
及其它語言的混合編程迟螺。
用法舉例<a id="orgheadline14"></a>
C++
引用 C
函數(shù)的具體例子<a id="orgheadline11"></a>
在 C++
中引用 C
語言中的函數(shù)和變量,在包含 C
語言頭文件(假設(shè)為 cExample.h
)時舍咖,需進行下列處理:
extern "C"
{
#include "cExample.h"
}
因為矩父, C
庫的編譯當(dāng)然是用 C
的方式生成的,其庫中的函數(shù)標(biāo)號一般也是類似前面所說的 _foo
之類的形式排霉,沒有任何參數(shù)信息窍株,所以當(dāng)然在 C++
中,要指定使用 extern "C" 攻柠,進行 C
方式的聲明(如果不指定夹姥,那么 C++
中的默認(rèn)聲明方式當(dāng)然是 C++
方式的,也就是編譯器會產(chǎn)生 _foo_int_int
之類包含參數(shù)信息的辙诞、 C++
形式的函數(shù)標(biāo)號辙售,這樣的函數(shù)標(biāo)號在已經(jīng)編譯好了的、可以直接引用的 C
庫中當(dāng)然沒有)飞涂。通過頭文件對函數(shù)進行聲明旦部,再包含頭文件,就能引用到頭文件中聲明的函數(shù)(因為函數(shù)的實現(xiàn)在庫中呢较店,所以只聲明士八,然后鏈接就能用了)。
而在 C
語言中梁呈,對其外部函數(shù)只能指定為 extern
類型婚度,因為 C
語言中不支持 extern "C" 聲明,在 .c
文件中包含了 extern "C" 時官卡,當(dāng)然會出現(xiàn)編譯語法錯誤蝗茁。
下面是一個具體代碼:
/* c語言頭文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern 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.cpp
extern "C"
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
可見,如果 C++
調(diào)用一個 C
語言編寫的 .DLL
時哮翘,在包含 .DLL
的頭文件或聲明接口函數(shù)時颈嚼,應(yīng)加 extern "C" { }
。這個時候饭寺,其實 extern "C" 是在告訴 C++
阻课,鏈接 C
庫的時候,采用 C
的方式進行鏈接(即尋找類似 _foo
的沒有參數(shù)信息的標(biāo)號艰匙,而不是默認(rèn)的 _foo_int_int
這樣包含了參數(shù)信息的 C++
標(biāo)號了)限煞。
C
引用 C++
函數(shù)的具體例子<a id="orgheadline12"></a>
在C中引用 C++
語言中的函數(shù)和變量時, C++
的頭文件需添加 extern "C" 员凝,但是在 C
語言中不能直接引用聲明了 extern "C" 的該頭文件(因為C語言不支持 extern "C" 關(guān)鍵字署驻,所以會報編譯錯誤),應(yīng)該僅在 C
文件中用 extern
聲明 C++
中定義的 extern "C" 函數(shù)(就是 C++
中用 extern "C" 聲明的函數(shù)绊序,在 C
中用 extern
來聲明一下硕舆,這樣 C
就能引用 C++
的函數(shù)了秽荞,但是 C
中是不用用 extern "C" 的)骤公。
下面是一個具體代碼:
//C++頭文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "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" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
上面例子, C
實現(xiàn)文件 cFile.c
不能直接用 #include "cExample.h"= 因為 =C
語言不支持 extern "C" 關(guān)鍵字扬跋。這個時候阶捆,而在 cppExample.h
中使用 extern "C" 修飾的目的是為了讓 C++
編譯時候能夠生成 C
形式的符號(類似 _foo
不含參數(shù)的形式),然后將其添加到對應(yīng)的 C++
實現(xiàn)庫中钦听,以便被 C
程序鏈接到洒试。
對 __BEGIN_DECLS
和 __END_DECLS
的理解<a id="orgheadline13"></a>
在 C
語言代碼中頭文件中,經(jīng)称由希看到充斥著下面的代碼片段:
1. __BEGIN_DECLS
2. .....
3. .....
4. __END_DECLS
其實垒棋,這些宏一般都是在標(biāo)準(zhǔn)庫頭文件中定義好了的,例如我當(dāng)前機器的 sys/cdefs.h
中大致定義如下:
1. #if defined(__cplusplus)
2. #define __BEGIN_DECLS extern "C" {
3. #define __END_DECLS }
4. #else
5. #define __BEGIN_DECLS
6. #define __END_DECLS
7. #endif
這目的當(dāng)然是擴充 C
語言在編譯的時候痪宰,按照 C++
編譯器進行統(tǒng)一處理叼架,使得 C++
代碼能夠調(diào)用 C
編譯生成的中間代碼。
由于 C
語言的頭文件可能被不同類型的編譯器讀取衣撬,因此寫 C
語言的頭文件必須慎重乖订。
總結(jié)<a id="orgheadline18"></a>
extern "C" 只是 C++
的關(guān)鍵字,不是 C
的<a id="orgheadline15"></a>
所以具练,如果在 C
程序中引入了 extern "C" 會導(dǎo)致編譯錯誤乍构。
被 extern "C" 修飾的目標(biāo)一般是對一個C或者 C++
函數(shù)的聲明<a id="orgheadline16"></a>
從源碼上看 extern "C" 一般對頭文件中函數(shù)聲明進行修飾。無論 C
程序中的還是 cpp
中的頭文件扛点,其函數(shù)聲明的形式都是一樣的(因為兩者語法基本一樣)哥遮,對應(yīng)聲明的實現(xiàn)卻可能由于相應(yīng)的程序特性而不同了( C
庫和 C++
庫里面當(dāng)然會不同)岂丘。
extern "C" 這個關(guān)鍵字聲明的真實目的,就是實現(xiàn) C++
與C及其它語言的混合編程<a id="orgheadline17"></a>
一旦被 extern "C" 修飾之后昔善,它便以 C
的方式工作元潘,可以實現(xiàn)在 C
中引用 C++
庫的函數(shù),也可以 C++
中引用 C
庫的函數(shù)君仆。
以上翩概,是對 extern "C" 這個關(guān)鍵字的理解和總結(jié),如果具體問題想要討論或者發(fā)現(xiàn)有何遺漏之處返咱,可以與我聯(lián)系钥庇。謝謝!