![2c762d1e-b019-11ec-aa7f-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/94/CB/wKgaomTl-iSAfQm7AACBsvnkBO4441.jpg)
![2c85a8d4-b019-11ec-aa7f-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/94/CB/wKgaomTl-iSAA10tAAEBImnDT1M532.jpg)
![2c9cc58c-b019-11ec-aa7f-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/94/CB/wKgaomTl-iSAEZYDAAAvAyztFUY126.jpg)
![2cb10858-b019-11ec-aa7f-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/94/CB/wKgaomTl-iSAG1pIAAEIy8hQQOs122.jpg)
![2cc6209e-b019-11ec-aa7f-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/94/CB/wKgaomTl-iWAZsfrAACZST3mfuw727.jpg)
2. 一組聲明的鏈接規(guī)范,比如:
對(duì)我們之前的例子而言,如果我們把頭文件my_handle.h的內(nèi)容改成:
然后使用C++編譯器重新編譯my_handle_client.cpp,所生成目標(biāo)文件my_handle_client.o中的符號(hào)表就變?yōu)椋?/span>
從中我們可以看出,此時(shí),用extern "C" 修飾了的聲明,其生成的符號(hào)和C語(yǔ)言編譯器生成的符號(hào)保持了一致。這樣,當(dāng)你再次把my_handle.o和my_handle_client.o放在一起鏈接的時(shí)候,就不會(huì)再有之前的“符號(hào)未定義”錯(cuò)誤了。
但此時(shí),如果你重新編譯my_handle.c,C語(yǔ)言編譯器將會(huì)報(bào)告“語(yǔ)法錯(cuò)誤”,因?yàn)閑xtern"C"是C++的語(yǔ)法,C語(yǔ)言編譯器不認(rèn)識(shí)它。此時(shí),可以按照我們之前已經(jīng)討論的,使用宏__cplusplus來(lái)識(shí)別C和C++編譯器。修改后的my_handle.h的代碼如下:
小心門后的未知世界
在我們清楚了 extern "C" 的來(lái)歷和用途之后,回到我們本來(lái)的話題上,為什么不能把#include 指令放置在 extern "C" { ... } 里面?我們先來(lái)看一個(gè)例子,現(xiàn)有a.h,b.h,c.h以及foo.cpp,其中foo.cpp包含c.h,c.h包含b.h,b.h包含a.h,如下:
現(xiàn)使用C++編譯器的預(yù)處理選項(xiàng)來(lái)編譯foo.cpp,得到下面的結(jié)果:
正如你看到的,當(dāng)你把#include指令放置在extern "C" { }里的時(shí)候,則會(huì)造成extern "C" { } 的嵌套。這種嵌套是被C++規(guī)范允許的。當(dāng)嵌套發(fā)生時(shí),以最內(nèi)層的嵌套為準(zhǔn)。比如在下面代碼中,函數(shù)foo會(huì)使用C++的鏈接規(guī)范,而函數(shù)bar則會(huì)使用C的鏈接規(guī)范。
如果能夠保證一個(gè)C語(yǔ)言頭文件直接或間接依賴的所有頭文件也都是C語(yǔ)言的,那么按照C++語(yǔ)言規(guī)范,這種嵌套應(yīng)該不會(huì)有什么問(wèn)題。但具體到某些編譯器的實(shí)現(xiàn),比如MSVC2005,卻可能由于 extern "C" { } 的嵌套過(guò)深而報(bào)告錯(cuò)誤。不要因此而責(zé)備微軟,因?yàn)榫瓦@個(gè)問(wèn)題而言,這種嵌套是毫無(wú)意義的。你完全可以通過(guò)把#include指令放置在extern "C" { }的外面來(lái)避免嵌套。拿之前的例子來(lái)說(shuō),如果我們把各個(gè)頭文件的 #include 指令都移到extern "C" { } 之外,然后使用C++編譯器的預(yù)處理選項(xiàng)來(lái)編譯foo.cpp,就會(huì)得到下面的結(jié)果:
這樣的結(jié)果肯定不會(huì)引起編譯問(wèn)題的結(jié)果——即便是使用MSVC。
把 #include 指令放置在extern "C" { }里面的另外一個(gè)重大風(fēng)險(xiǎn)是,你可能會(huì)無(wú)意中改變一個(gè)函數(shù)聲明的鏈接規(guī)范。比如:有兩個(gè)頭文件a.h,b.h,其中b.h包含a.h,如下:
按照a.h作者的本意,函數(shù)foo是一個(gè)C++自由函數(shù),其鏈接規(guī)范為"C++"。但在b.h中,由于#include "a.h"被放到了extern "C" { }的內(nèi)部,函數(shù)foo的鏈接規(guī)范被不正確地更改了。
由于每一條 #include 指令后面都隱藏這一個(gè)未知的世界,除非你刻意去探索,否則你永遠(yuǎn)都不知道,當(dāng)你把一條條#include指令放置于extern "C" { }里面的時(shí)候,到底會(huì)產(chǎn)生怎樣的結(jié)果,會(huì)帶來(lái)何種的風(fēng)險(xiǎn)。或許你會(huì)說(shuō),“我可以去查看這些被包含的頭文件,我可以保證它們不會(huì)帶來(lái)麻煩”。但,何必呢?畢竟,我們完全可以不必為不必要的事情買單,不是嗎?
Q&A
Q: 難道任何#include指令都不能放在extern "C"里面嗎?A: 正像這個(gè)世界的大多數(shù)規(guī)則一樣,總會(huì)存在特殊情況。
有時(shí)候,你可能利用頭文件機(jī)制“巧妙”的解決一些問(wèn)題。比如,#pragma pack的問(wèn)題。這些頭文件和常規(guī)的頭文件作用是不一樣的,它們里面不會(huì)放置C的函數(shù)聲明或者變量定義,鏈接規(guī)范不會(huì)對(duì)它們的內(nèi)容產(chǎn)生影響。這種情況下,你可以不必遵守這些規(guī)則。
更加一般的原則是,在你明白了這所有的原理之后,只要你明白自己在干什么,那就去做吧。
Q: 你只說(shuō)了不應(yīng)該放入extern "C"的,但什么可以放入呢?
A: 鏈接規(guī)范僅僅用于修飾函數(shù)和變量,以及函數(shù)類型。所以,嚴(yán)格的講,你只應(yīng)該把這三種對(duì)象放置于extern "C"的內(nèi)部。
但,你把C語(yǔ)言的其它元素,比如非函數(shù)類型定義(結(jié)構(gòu)體,枚舉等)放入extern "C"內(nèi)部,也不會(huì)帶來(lái)任何影響。更不用說(shuō)宏定義預(yù)處理指令了。
所以,如果你更加看重良好組織和管理的習(xí)慣,你應(yīng)該只在必須使用extern "C"聲明的地方使用它。即使你比較懶惰,絕大多數(shù)情況下,把一個(gè)頭件自身的所有定義和聲明都放置在extern"C"里面也不會(huì)有太大的問(wèn)題。
Q: 如果一個(gè)帶有函數(shù)/變量聲明的C頭文件里沒有extern "C"聲明怎么辦?
A: 如果你可以判斷,這個(gè)頭文件永遠(yuǎn)不可能讓C++代碼來(lái)使用,那么就不要管它。
但現(xiàn)實(shí)是,大多數(shù)情況下,你無(wú)法準(zhǔn)確的推測(cè)未來(lái)。你在現(xiàn)在就加上這個(gè)extern "C",這花不了你多少成本,但如果你現(xiàn)在沒有加,等到將來(lái)這個(gè)頭文件無(wú)意中被別人的C++程序包含的時(shí)候,別人很可能需要更高的成本來(lái)定位錯(cuò)誤和修復(fù)問(wèn)題。
Q: 如果我的C+ +程序想包含一個(gè)C頭文件a . h,它的內(nèi)容包含了C的函數(shù)/變量聲明,但它們卻沒有使用extern "C"鏈接規(guī)范,該怎么辦?
A: 在a.h里面加上它。
某些人可能會(huì)建議你,如果a.h沒有extern "C",而b.cpp包含了a.h,可以在b.cpp里加上 :
這是一個(gè)邪惡的方案,原因在之前我們已經(jīng)闡述。但值得探討的是,這種方案這背后卻可能隱含著一個(gè)假設(shè),即我們不能修改a.h。不能修改的原因可能來(lái)自兩個(gè)方面:
-
頭文件代碼屬于其它團(tuán)隊(duì)或者第三方公司,你沒有修改代碼的權(quán)限;
-
雖然你擁有修改代碼的權(quán)限,但由于這個(gè)頭文件屬于遺留系統(tǒng),冒然修改可能會(huì)帶來(lái)不可預(yù)知的問(wèn)題。
對(duì)于第一種情況,不要試圖自己進(jìn)行workaround,因?yàn)檫@會(huì)給你帶來(lái)不必要的麻煩。正確的解決方案是,把它當(dāng)作一個(gè)bug,發(fā)送缺陷報(bào)告給相應(yīng)的團(tuán)隊(duì) 或第三方公司。如果是自己公司的團(tuán)隊(duì)或你已經(jīng)付費(fèi)的第三方公司,他們有義務(wù)為你進(jìn)行這樣的修改。如果他們不明白這件事情的重要性,告訴他們。如果這些頭文 件屬于一個(gè)免費(fèi)開源軟件,自己進(jìn)行正確的修改,并發(fā)布patch給其開發(fā)團(tuán)隊(duì)。
在第二種情況下,你需要拋棄掉這種不必要的安全意識(shí)。因?yàn)椋紫龋瑢?duì)于大多數(shù)頭文件而言,這種修改都不是一種復(fù)雜的,高風(fēng)險(xiǎn)的修改,一切都在可控的范圍之 內(nèi);其次,如果某個(gè)頭文件混亂而復(fù)雜,雖然對(duì)于遺留系統(tǒng)的哲學(xué)應(yīng)該是:“在它還沒有帶來(lái)麻煩之前不要?jiǎng)铀保F(xiàn)在麻煩已經(jīng)來(lái)了,逃避不如正視,所以上策 是,將其視作一個(gè)可以整理到干凈合理狀態(tài)的良好機(jī)會(huì)。
Q: 我們代碼中關(guān)于extern "C"的寫法如下,這正確嗎?
A: 不確定。
按照C++的規(guī)范定義,__cplusplus 的值應(yīng)該被定義為199711L,這是一個(gè)非零的值;盡管某些編譯器并沒有按照規(guī)范來(lái)實(shí)現(xiàn),但仍然能夠保證__cplusplus的值為非零——至少我到目前為止還沒有看到哪款編譯器將其實(shí)現(xiàn)為0。這種情況下,#if __cplusplus ... #endif完全是冗余的。
但,C++編譯器的廠商是如此之多,沒有人可以保證某款編譯器,或某款編譯器的早期版本沒有將__cplusplus的值定義為0。但即便如此,只要能夠保證宏__cplusplus只在C++編譯器中被預(yù)先定義 ,那么,僅僅使用#ifdef __cplusplus ? #endif就足以確保意圖的正確性;額外的使用#if __cplusplus ... #endif反而是錯(cuò)誤的。
只有在這種情況下:即某個(gè)廠商的C語(yǔ)言和C++語(yǔ)言編譯器都預(yù)先定義了__cplusplus ,但通過(guò)其值為0和非零來(lái)進(jìn)行區(qū)分,使用#if __cplusplus ... #endif才是正確且必要的。
既然現(xiàn)實(shí)世界是如此復(fù)雜,你就需要明確自己的目標(biāo),然后根據(jù)目標(biāo)定義相應(yīng)的策略。比如:如果你的目標(biāo)是讓你的代碼能夠使用幾款主流的、正確遵守了規(guī)范的編譯器進(jìn)行編譯,那么你只需要簡(jiǎn)單的使用#ifdef __cplusplus ... #endif就足夠了。
但如果你的產(chǎn)品是一個(gè)雄心勃勃的,試圖兼容各種編譯器的(包括未知的)跨平臺(tái)產(chǎn)品, 我們可能不得不使用下述方法來(lái)應(yīng)對(duì)各種情況 ,其中__ALIEN_C_LINKAGE__是為了標(biāo)識(shí)那些在C和C++編譯中都定義了__cplusplus宏的編譯器。
這應(yīng)該可以工作,但在每個(gè)頭文件中都寫這么一大串,不僅有礙觀瞻,還會(huì)造成一旦策略進(jìn)行修改,就會(huì)到處修改的狀況。違反了DRY(Don't Repeat Yourself)原則,你總要為之付出額外的代價(jià)。解決它的一個(gè)簡(jiǎn)單方案是,定義一個(gè)特定的頭文件——比如clinkage.h,在其中增加這樣的定義:
以下舉例中c的函數(shù)聲明和定義分別在cfun.h 和 cfun.c 中,函數(shù)打印字符串 “this is c fun call”,c++函數(shù)聲明和定義分別在cppfun.h 和 cppfun.cpp中,函數(shù)打印字符串 "this is cpp fun call", 編譯環(huán)境vc2010.
C++ 調(diào)用 C 的方法
c++ 調(diào)用 c 的方法,關(guān)鍵是要讓c的函數(shù)按照c的方式編譯,而不是c++的方式。(1) cfun.h如下:
cppfun.cpp 如下:
?
(2)cfun.h同上cppfun.cpp 如下:extern"C"{#include"cfun.h"http://注意include語(yǔ)句一定要單獨(dú)占一行;}#include"cppfun.h"#include
(3)cfun.h如下:
#ifndef_C_FUN_H_#define_C_FUN_H_ #ifdef__cplusplusextern"C"{#endif voidcfun(); #ifdef__cplusplus}#endif #endifcppfun.cpp如下:
#include"cfun.h"#include"cppfun.h"#includeC調(diào)用C++的方法
c調(diào)用c++,關(guān)鍵是C++ 提供一個(gè)符合 C 調(diào)用慣例的函數(shù)。
在vs2010上測(cè)試時(shí),沒有聲明什么extern等,只在在cfun.c中包含cppfun.h,然后調(diào)用cppfun()也可以編譯運(yùn)行,在gcc下就編譯出錯(cuò),按照c++/c的標(biāo)準(zhǔn)這種做法應(yīng)該是錯(cuò)誤的。以下方法兩種編譯器都可以運(yùn)行。
cppfun.h如下:
#ifndef_CPP_FUN_H_#define_CPP_FUN_H_ extern"C"voidcppfun(); #endifcfun.c如下:
//#include"cppfun.h"http://不要包含頭文件,否則編譯出錯(cuò)#include"cfun.h"#include審核編輯 :李倩
-
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7615瀏覽量
137851 -
C++
+關(guān)注
關(guān)注
22文章
2114瀏覽量
73890 -
extern
+關(guān)注
關(guān)注
0文章
7瀏覽量
2966
原文標(biāo)題:C語(yǔ)言中的extern "C"
文章出處:【微信號(hào):c-stm32,微信公眾號(hào):STM32嵌入式開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
EE-62:在C語(yǔ)言中訪問(wèn)短字內(nèi)存
![EE-62:在<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言中</b>訪問(wèn)短字內(nèi)存](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
EE-128:C語(yǔ)言中的DSP:從C調(diào)用匯編類成員函數(shù)
![EE-128:<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言中</b>的DSP:從<b class='flag-5'>C</b>調(diào)用匯編類成員函數(shù)](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
C語(yǔ)言中申請(qǐng)的堆內(nèi)存能不能自動(dòng)釋放
C語(yǔ)言中的頭文件能不能重復(fù)包含
技術(shù)干貨驛站 ▏深入理解C語(yǔ)言:掌握C語(yǔ)言條件判斷,從if到switch的應(yīng)用
![技術(shù)干貨驛站 ▏深入理解<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>:掌握<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>條件判斷,從if到switch的應(yīng)用](https://file1.elecfans.com/web2/M00/FC/CD/wKgZomaWI5uASgBaAABuQHdMO4I302.png)
C語(yǔ)言中的socket編程基礎(chǔ)
c語(yǔ)言中從左到右結(jié)合怎么看
嵌入式系統(tǒng)中C語(yǔ)言結(jié)構(gòu)體的基礎(chǔ)實(shí)現(xiàn)與應(yīng)用
![嵌入式系統(tǒng)中<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>結(jié)構(gòu)體的基礎(chǔ)實(shí)現(xiàn)與應(yīng)用](https://file1.elecfans.com/web2/M00/C4/E6/wKgaomXv9r6Aaj6DAAAYJ3PfWAU110.jpg)
C語(yǔ)言中的typedef的應(yīng)用
![<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言中</b>的typedef的應(yīng)用](https://file1.elecfans.com/web2/M00/C3/B9/wKgaomXn5LGAbFesAAAe4pvOxcc159.png)
C語(yǔ)言#define的應(yīng)用
![<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>#define的應(yīng)用](https://file1.elecfans.com/web2/M00/C3/B9/wKgaomXn42eACLumAAAag1PydyU214.png)
介紹C語(yǔ)言中錯(cuò)誤處理和異常處理的一些常用的方法和策略
C語(yǔ)言中的可變參數(shù)介紹
![<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言中</b>的可變參數(shù)介紹](https://file1.elecfans.com/web2/M00/C2/BA/wKgaomXezG6AcBLhAABI4KQSKz0893.png)
C語(yǔ)言中的錯(cuò)誤處理機(jī)制解析
C語(yǔ)言中的動(dòng)態(tài)內(nèi)存管理講解
![<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言中</b>的動(dòng)態(tài)內(nèi)存管理講解](https://file1.elecfans.com/web2/M00/C0/BB/wKgZomXYNZuATvacAABRO_zLRlc245.png)
評(píng)論