色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

C語言中的extern

STM32嵌入式開發 ? 來源:STM32嵌入式開發 ? 作者:STM32嵌入式開發 ? 2022-04-02 16:03 ? 次閱讀
在你的C語言代碼中,不知能否看到類似下面的代碼:

2c60a048-b019-11ec-aa7f-dac502259ad0.jpg

這好像沒有什么問題,你應該還會想:“嗯?是啊,我們的代碼都是這樣寫的,從來沒有因此碰到過什么麻煩啊~”。你說的沒錯,如果你的頭文件從來沒有被任何C++程序引用過的話。這與C++有什么關系呢? 看看__cplusplus(注意前面是兩個下劃線) 的名字你就應該知道它與C++有很大關系。__cplusplus是一個C++規范規定的預定義宏。你可以信任的是:所有的現代C++編譯器都預先定義了它;而所有C語言編譯器則不會。另外,按照規范__cplusplus的值應該等于1 9 9 7 1 1 L ,然而不是所有的編譯器都照此實現,比如g++編譯器就將它的值定義為1。所以,如果上述代碼被C語言程序引用的話,它的內容就等價于下列代碼。2c762d1e-b019-11ec-aa7f-dac502259ad0.jpg在這種情況下,既然extern "C" { }經過預處理之后根本就不存在,那么它和#include指令之間的關系問題自然也就是無中生有。相關推薦:整理的比較全面的C語言入門筆記!extern "C"的前世今生在C++編譯器里,有一位暗黑破壞神,專門從事一份稱作“名字粉碎”(name mangling)的工作。當把一個C++的源文件投入編譯的時候,它就開始工作,把每一個它在源文件里看到的外部可見的名字粉碎的面目全非,然后存儲到二進制目標文件的符號表里。之所以在C++的世界里存在這樣一個怪物,是因為C++允許對一個名字給予不同的定義,只要在語義上沒有二義性就好。比如,你可以讓兩個函數是同名的,只要它們的參數列表不同即可,這就是函數重載(function overloading);甚至,你可以讓兩個函數的原型聲明是完全相同的,只要它們所處的名字空間(namespace)不一樣即可。事實上,當處于不同的名字空間時,所有的名字都是可以重復的,無論是函數名,變量名,還是類型名。另外,C++程序的構造方式仍然繼承了C語言的傳統:編譯器把每一個通過命令行指定的源代碼文件看做一個獨立的編譯單元,生成目標文件;然后,鏈接器通過查找這些目標文件的符號表將它們鏈接在一起生成可執行程序。相關文章:C語言編譯過程編譯和鏈接是兩個階段的事情。事實上,編譯器和鏈接器是兩個完全獨立的工具。編譯器可以通過語義分析知道那些同名的符號之間的差別;而鏈接器卻只能通過目標文件符號表中保存的名字來識別對象。所以,編譯器進行名字粉碎的目的是為了讓鏈接器在工作的時候不陷入困惑,將所有名字重新編碼,生成全局唯一,不重復的新名字,讓鏈接器能夠準確識別每個名字所對應的對象。但 C語言卻是一門單一名字空間的語言,也不允許函數重載,也就是說,在一個編譯和鏈接的范圍之內,C語言不允許存在同名對象。比如,在一個編譯單元內部,不允許存在同名的函數,無論這個函數是否用static修飾;在一個可執行程序對應的所有目標文件里,不允許存在同名對象,無論它代表一個全局變量,還是一個函數。所以,C語言編譯器不需要對任何名字進行復雜的處理(或者僅僅對名字進行簡單一致的修飾(decoration),比如在名字前面統一的加上單下劃線_)。 C++的締造者Bjarne Stroustrup在最初就把——能夠兼容C,能夠復用大量已經存在的C庫——列為C++語言的重要目標。但兩種語言的編譯器對待名字的處理方式是不一致的,這就給鏈接過程帶來了麻煩。例如,現有一個名為my_handle.h的頭文件,內容如下:2c85a8d4-b019-11ec-aa7f-dac502259ad0.jpg然后使用C語言編譯器編譯my_handle.c,生成目標文件my_handle.o。由于C語言編譯器不對名字進行粉碎,所以在my_handle.o的符號表里,這三個函數的名字和源代碼文件中的聲明是一致的。2c9cc58c-b019-11ec-aa7f-dac502259ad0.jpg隨后,我們想讓一個C++程序調用這些函數,所以,它也包含了頭文件my_handle.h。假設這個C++源代碼文件的名字叫my_handle_client.cpp,其內容如下:2cb10858-b019-11ec-aa7f-dac502259ad0.jpg其中,粗體的部分就是那三個函數的名字被粉碎后的樣子。然后,為了讓程序可以工作,你必須將my_handle.o和my_handle_client.o放在一起鏈接。由于在兩個目標文件對于同一對象的命名不一樣,鏈接器將報告相關的“符號未定義”錯誤。2cc6209e-b019-11ec-aa7f-dac502259ad0.jpg為了解決這一問題,C++引入了鏈接規范(linkage specification)的概念,表示法為extern"language string",C++編譯器普遍支持的"language string"有"C"和"C++",分別對應C語言和C++語言。鏈接規范的作用是告訴C++編譯:對于所有使用了鏈接規范進行修飾的聲明或定義,應該按照指定語言的方式來處理,比如名字,調用習慣(calling convention)等等。鏈接規范的用法有兩種 1.單個聲明的鏈接規范,比如:

2cd59272-b019-11ec-aa7f-dac502259ad0.png

2. 一組聲明的鏈接規范,比如:

2ce864c4-b019-11ec-aa7f-dac502259ad0.png

對我們之前的例子而言,如果我們把頭文件my_handle.h的內容改成:

2cfde236-b019-11ec-aa7f-dac502259ad0.jpg

然后使用C++編譯器重新編譯my_handle_client.cpp,所生成目標文件my_handle_client.o中的符號表就變為:

2d190ce6-b019-11ec-aa7f-dac502259ad0.jpg

從中我們可以看出,此時,用extern "C" 修飾了的聲明,其生成的符號和C語言編譯器生成的符號保持了一致。這樣,當你再次把my_handle.o和my_handle_client.o放在一起鏈接的時候,就不會再有之前的“符號未定義”錯誤了。

但此時,如果你重新編譯my_handle.c,C語言編譯器將會報告“語法錯誤”,因為extern"C"是C++的語法,C語言編譯器不認識它。此時,可以按照我們之前已經討論的,使用宏__cplusplus來識別C和C++編譯器。修改后的my_handle.h的代碼如下:

2d2b3934-b019-11ec-aa7f-dac502259ad0.jpg

小心門后的未知世界

在我們清楚了 extern "C" 的來歷和用途之后,回到我們本來的話題上,為什么不能把#include 指令放置在 extern "C" { ... } 里面?

我們先來看一個例子,現有a.h,b.h,c.h以及foo.cpp,其中foo.cpp包含c.h,c.h包含b.h,b.h包含a.h,如下:

2d42fae2-b019-11ec-aa7f-dac502259ad0.jpg

現使用C++編譯器的預處理選項來編譯foo.cpp,得到下面的結果:

2d54b156-b019-11ec-aa7f-dac502259ad0.png

正如你看到的,當你把#include指令放置在extern "C" { }里的時候,則會造成extern "C" { } 的嵌套。這種嵌套是被C++規范允許的。當嵌套發生時,以最內層的嵌套為準。比如在下面代碼中,函數foo會使用C++的鏈接規范,而函數bar則會使用C的鏈接規范。

2d674726-b019-11ec-aa7f-dac502259ad0.jpg

如果能夠保證一個C語言頭文件直接或間接依賴的所有頭文件也都是C語言的,那么按照C++語言規范,這種嵌套應該不會有什么問題。但具體到某些編譯器的實現,比如MSVC2005,卻可能由于 extern "C" { } 的嵌套過深而報告錯誤。不要因此而責備微軟,因為就這個問題而言,這種嵌套是毫無意義的。你完全可以通過把#include指令放置在extern "C" { }的外面來避免嵌套。拿之前的例子來說,如果我們把各個頭文件的 #include 指令都移到extern "C" { } 之外,然后使用C++編譯器的預處理選項來編譯foo.cpp,就會得到下面的結果:

2d7be2ee-b019-11ec-aa7f-dac502259ad0.jpg

這樣的結果肯定不會引起編譯問題的結果——即便是使用MSVC。

把 #include 指令放置在extern "C" { }里面的另外一個重大風險是,你可能會無意中改變一個函數聲明的鏈接規范。比如:有兩個頭文件a.h,b.h,其中b.h包含a.h,如下:

2d95f31e-b019-11ec-aa7f-dac502259ad0.jpg

按照a.h作者的本意,函數foo是一個C++自由函數,其鏈接規范為"C++"。但在b.h中,由于#include "a.h"被放到了extern "C" { }的內部,函數foo的鏈接規范被不正確地更改了。

由于每一條 #include 指令后面都隱藏這一個未知的世界,除非你刻意去探索,否則你永遠都不知道,當你把一條條#include指令放置于extern "C" { }里面的時候,到底會產生怎樣的結果,會帶來何種的風險。或許你會說,“我可以去查看這些被包含的頭文件,我可以保證它們不會帶來麻煩”。但,何必呢?畢竟,我們完全可以不必為不必要的事情買單,不是嗎?

Q&A

Q: 難道任何#include指令都不能放在extern "C"里面嗎?

A: 正像這個世界的大多數規則一樣,總會存在特殊情況。

有時候,你可能利用頭文件機制“巧妙”的解決一些問題。比如,#pragma pack的問題。這些頭文件和常規的頭文件作用是不一樣的,它們里面不會放置C的函數聲明或者變量定義,鏈接規范不會對它們的內容產生影響。這種情況下,你可以不必遵守這些規則。

更加一般的原則是,在你明白了這所有的原理之后,只要你明白自己在干什么,那就去做吧。

Q: 你只說了不應該放入extern "C"的,但什么可以放入呢?

A: 鏈接規范僅僅用于修飾函數和變量,以及函數類型。所以,嚴格的講,你只應該把這三種對象放置于extern "C"的內部。

但,你把C語言的其它元素,比如非函數類型定義(結構體,枚舉等)放入extern "C"內部,也不會帶來任何影響。更不用說宏定義預處理指令了。

所以,如果你更加看重良好組織和管理的習慣,你應該只在必須使用extern "C"聲明的地方使用它。即使你比較懶惰,絕大多數情況下,把一個頭件自身的所有定義和聲明都放置在extern"C"里面也不會有太大的問題。

Q: 如果一個帶有函數/變量聲明的C頭文件里沒有extern "C"聲明怎么辦?

A: 如果你可以判斷,這個頭文件永遠不可能讓C++代碼來使用,那么就不要管它。

但現實是,大多數情況下,你無法準確的推測未來。你在現在就加上這個extern "C",這花不了你多少成本,但如果你現在沒有加,等到將來這個頭文件無意中被別人的C++程序包含的時候,別人很可能需要更高的成本來定位錯誤和修復問題。

Q: 如果我的C+ +程序想包含一個C頭文件a . h,它的內容包含了C的函數/變量聲明,但它們卻沒有使用extern "C"鏈接規范,該怎么辦?

A: 在a.h里面加上它。

某些人可能會建議你,如果a.h沒有extern "C",而b.cpp包含了a.h,可以在b.cpp里加上 :

2daabce0-b019-11ec-aa7f-dac502259ad0.png

這是一個邪惡的方案,原因在之前我們已經闡述。但值得探討的是,這種方案這背后卻可能隱含著一個假設,即我們不能修改a.h。不能修改的原因可能來自兩個方面:

  • 頭文件代碼屬于其它團隊或者第三方公司,你沒有修改代碼的權限;

  • 雖然你擁有修改代碼的權限,但由于這個頭文件屬于遺留系統,冒然修改可能會帶來不可預知的問題。

對于第一種情況,不要試圖自己進行workaround,因為這會給你帶來不必要的麻煩。正確的解決方案是,把它當作一個bug,發送缺陷報告給相應的團隊 或第三方公司。如果是自己公司的團隊或你已經付費的第三方公司,他們有義務為你進行這樣的修改。如果他們不明白這件事情的重要性,告訴他們。如果這些頭文 件屬于一個免費開源軟件,自己進行正確的修改,并發布patch給其開發團隊。

在第二種情況下,你需要拋棄掉這種不必要的安全意識。因為,首先,對于大多數頭文件而言,這種修改都不是一種復雜的,高風險的修改,一切都在可控的范圍之 內;其次,如果某個頭文件混亂而復雜,雖然對于遺留系統的哲學應該是:“在它還沒有帶來麻煩之前不要動它”,但現在麻煩已經來了,逃避不如正視,所以上策 是,將其視作一個可以整理到干凈合理狀態的良好機會。

Q: 我們代碼中關于extern "C"的寫法如下,這正確嗎?

2dbdd708-b019-11ec-aa7f-dac502259ad0.jpg

A: 不確定。

按照C++的規范定義,__cplusplus 的值應該被定義為199711L,這是一個非零的值;盡管某些編譯器并沒有按照規范來實現,但仍然能夠保證__cplusplus的值為非零——至少我到目前為止還沒有看到哪款編譯器將其實現為0。這種情況下,#if __cplusplus ... #endif完全是冗余的。

但,C++編譯器的廠商是如此之多,沒有人可以保證某款編譯器,或某款編譯器的早期版本沒有將__cplusplus的值定義為0。但即便如此,只要能夠保證宏__cplusplus只在C++編譯器中被預先定義 ,那么,僅僅使用#ifdef __cplusplus ? #endif就足以確保意圖的正確性;額外的使用#if __cplusplus ... #endif反而是錯誤的。

只有在這種情況下:即某個廠商的C語言和C++語言編譯器都預先定義了__cplusplus ,但通過其值為0和非零來進行區分,使用#if __cplusplus ... #endif才是正確且必要的。

既然現實世界是如此復雜,你就需要明確自己的目標,然后根據目標定義相應的策略。比如:如果你的目標是讓你的代碼能夠使用幾款主流的、正確遵守了規范的編譯器進行編譯,那么你只需要簡單的使用#ifdef __cplusplus ... #endif就足夠了。

但如果你的產品是一個雄心勃勃的,試圖兼容各種編譯器的(包括未知的)跨平臺產品, 我們可能不得不使用下述方法來應對各種情況 ,其中__ALIEN_C_LINKAGE__是為了標識那些在C和C++編譯中都定義了__cplusplus宏的編譯器。

2dd46234-b019-11ec-aa7f-dac502259ad0.png

這應該可以工作,但在每個頭文件中都寫這么一大串,不僅有礙觀瞻,還會造成一旦策略進行修改,就會到處修改的狀況。違反了DRY(Don't Repeat Yourself)原則,你總要為之付出額外的代價。解決它的一個簡單方案是,定義一個特定的頭文件——比如clinkage.h,在其中增加這樣的定義:

2deb9a6c-b019-11ec-aa7f-dac502259ad0.jpg

以下舉例中c的函數聲明和定義分別在cfun.h 和 cfun.c 中,函數打印字符串 “this is c fun call”,c++函數聲明和定義分別在cppfun.h 和 cppfun.cpp中,函數打印字符串 "this is cpp fun call", 編譯環境vc2010.

C++ 調用 C 的方法

c++ 調用 c 的方法,關鍵是要讓c的函數按照c的方式編譯,而不是c++的方式。(1) cfun.h如下:

2dffbc54-b019-11ec-aa7f-dac502259ad0.png

cppfun.cpp 如下:

2e156978-b019-11ec-aa7f-dac502259ad0.png

?

(2)cfun.h同上

cppfun.cpp 如下:extern"C"{#include"cfun.h"http://注意include語句一定要單獨占一行;}#include"cppfun.h"#includeusingnamespacestd; voidcppfun(){cout<<"this?is?cpp?fun?call"<} intmain(){cfun();return0;}

(3)cfun.h如下:

#ifndef_C_FUN_H_#define_C_FUN_H_ #ifdef__cplusplusextern"C"{#endif voidcfun(); #ifdef__cplusplus}#endif #endif

cppfun.cpp如下:

#include"cfun.h"#include"cppfun.h"#includeusingnamespacestd; voidcppfun(){cout<<"this?is?cpp?fun?call"<} intmain(){cfun();return0;}

C調用C++的方法

c調用c++,關鍵是C++ 提供一個符合 C 調用慣例的函數。

在vs2010上測試時,沒有聲明什么extern等,只在在cfun.c中包含cppfun.h,然后調用cppfun()也可以編譯運行,在gcc下就編譯出錯,按照c++/c的標準這種做法應該是錯誤的。以下方法兩種編譯器都可以運行。

cppfun.h如下:

#ifndef_CPP_FUN_H_#define_CPP_FUN_H_ extern"C"voidcppfun(); #endif

cfun.c如下:

//#include"cppfun.h"http://不要包含頭文件,否則編譯出錯#include"cfun.h"#include voidcfun(){printf("thisiscfuncall ");} externvoidcppfun(); intmain(){#ifdef__cpluspluscfun();#endifcppfun();return0;}

審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • C語言
    +關注

    關注

    180

    文章

    7605

    瀏覽量

    136995
  • C++
    C++
    +關注

    關注

    22

    文章

    2110

    瀏覽量

    73687
  • extern
    +關注

    關注

    0

    文章

    7

    瀏覽量

    2954

原文標題:C語言中的extern "C"

文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    C語言中申請的堆內存能不能自動釋放

    C語言中申請的堆內存能不能自動釋放?每次都要手動 free 太麻煩,也容易忘記。 學過 C++ 的同學,應該首先能想到智能指針。 但是這是C語言
    的頭像 發表于 11-27 09:33 ?123次閱讀

    C語言中的頭文件能不能重復包含

    C語言中的頭文件能不能重復包含? 比如代碼寫成這樣,stdio.h 連續包含了兩次。 #include #include int main(){ printf("helloworld
    的頭像 發表于 11-26 17:19 ?180次閱讀

    技術干貨驛站 ▏深入理解C語言:掌握C語言條件判斷,從if到switch的應用

    在編程中,條件判斷語句是控制程序流程的核心元素之一。它們使得程序能夠根據不同的輸入和狀態,做出相應的決策。特別是在C語言中,條件判斷語句的使用極為廣泛,涵蓋了從簡單的if語句到更復雜的switch
    的頭像 發表于 11-09 01:10 ?358次閱讀
    技術干貨驛站 ▏深入理解<b class='flag-5'>C</b><b class='flag-5'>語言</b>:掌握<b class='flag-5'>C</b><b class='flag-5'>語言</b>條件判斷,從if到switch的應用

    C語言中的socket編程基礎

    Socket編程簡介 Socket是一種通信機制,允許程序之間進行通信。在C語言中,socket編程是網絡編程的基礎。通過使用socket,程序可以發送和接收數據,實現不同計算機之間的通信
    的頭像 發表于 11-01 16:51 ?344次閱讀

    C語言中最常見的宏定義寫法

    如果讓你用C語言寫個宏定義,我相信大部分同學順手就能寫出define。
    的頭像 發表于 10-28 11:12 ?293次閱讀

    c語言中從左到右結合怎么看

    C語言中,操作符的結合性(Associativity)是指當操作符在表達式中連續出現時,它們如何與操作數結合的順序。對于大多數二元操作符(即需要兩個操作數的操作符),C語言遵循兩種基
    的頭像 發表于 08-20 11:42 ?912次閱讀

    嵌入式系統中C語言結構體的基礎實現與應用

    C語言中的數組只能允許程序員定義存儲相同類型數據。但是結構是C語言編程中允許您存儲不同數據類型的數據。
    發表于 03-12 14:29 ?510次閱讀
    嵌入式系統中<b class='flag-5'>C</b><b class='flag-5'>語言</b>結構體的基礎實現與應用

    C語言中的typedef的應用

    C 語言提供了 typedef 關鍵字,您可以使用它來為類型取一個新的名字。下面的實例為單字節數字定義了一個術語 BYTE。
    發表于 03-06 11:34 ?390次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言中</b>的typedef的應用

    C語言#define的應用

    C/C++ 編程語言中,當程序被編譯時,被發送到編譯器,編譯器將程序轉換為機器語言,然后完成編譯并執行該程序。預處理器也稱為宏預處理器。
    發表于 03-06 11:29 ?386次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>#define的應用

    介紹C語言中錯誤處理和異常處理的一些常用的方法和策略

    C語言是一種低級的、靜態的、結構化的編程語言,它沒有提供像C++或Java等高級語言中的異常處理機制,例如try-catch-finally
    的頭像 發表于 02-28 14:25 ?639次閱讀

    C語言中的可變參數介紹

    C 語言為這種情況提供了一個解決方案,它允許您定義一個函數,能根據具體的需求接受可變數量的參數
    發表于 02-28 14:00 ?322次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言中</b>的可變參數介紹

    C語言中的錯誤處理機制解析

    C 語言不提供對錯誤處理的直接支持,但是作為一種系統編程語言,它以返回值的形式允許您訪問底層數據。
    的頭像 發表于 02-26 11:19 ?521次閱讀

    C語言中的動態內存管理講解

    本章將講解 C 中的動態內存管理。C 語言為內存的分配和管理提供了幾個函數。這些函數可以在 頭文件中找到。
    的頭像 發表于 02-23 14:03 ?398次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言中</b>的動態內存管理講解

    枚舉有多大?c語言枚舉end的作用是什么?

    枚舉有多大?c語言枚舉end的作用是什么? 枚舉在C語言中是一種常見的數據類型,用于定義一組相互關聯的常量或者變量。它通常用于表示一系列可能的取值,使得程序更加易讀和易維護。在
    的頭像 發表于 01-19 14:19 ?613次閱讀

    如何解決C語言中的“訪問權限沖突”異常?C語言引發異常原因分析

    如何解決C語言中的“訪問權限沖突”異常?C語言引發異常原因分析? 在C語言中,訪問權限沖突異常通
    的頭像 發表于 01-12 16:03 ?5808次閱讀
    主站蜘蛛池模板: 久久国产精品自线拍免费| 日日夜夜天天操| 亚洲黄色大片| 九九热国产视频| 97超视频在线观看| 日韩精品卡1卡2三卡四卡乱码| 丰满的美女射精动态图| 亚洲 无码 在线 专区| 久久99综合国产精品亚洲首页| 99国产精品人妻无码免费| 视频区 国产 欧美 日韩| 久久精品免费观看久久| 超碰免费视频部落格| 亚洲欧美日韩国产手机在线| 男男高H啪肉Np文多攻多一受| 国产成人精视频在线观看免费| 亚洲精品在线网址| 欧美性狂猛AAAAAA| 吉吉影音先锋av资源网| Y8848高清私人影院软件优势| 亚洲精品久久久久AV无码| 女生下面免费看| 韩国黄色影院| 成人无码在线超碰视频| 尹人综合网| 无码AV免费精品一区二区三区| 久久秋霞理伦片| 国产精品免费大片一区二区| 337p啪啪人体大胆| 亚洲精品色情APP在线下载观看| 欧美日韩亚洲第一区在线| 精品性影院一区二区三区内射| 成人性生交大片免费看4| 曰本xxⅹ孕妇性xxx| 午夜福利体验免费体验区| 欧美00后rapper潮水| 精品免费在线视频| 国产精品日本欧美一区二区| 99久久国产综合精品国| 亚洲日本一区二区三区在线不卡 | 久久精品综合电影|