2008-09-15 135 views
117

爲什麼我們需要使用:爲什麼我們需要在C++中使用extern「C」{#include <foo.h>}?

extern "C" { 
#include <foo.h> 
} 

具體做法是:

  • 我們什麼時候應該使用它?

  • 在編譯器/鏈接器級別發生什麼事情需要我們使用它?

  • 在編譯/鏈接方面,這是否解決了需要我們使用它的問題?

+0

我很困惑你的問題題目是什麼意思......你能詳細說明一下嗎? – 2008-09-15 23:21:27

+29

我不知道該怎麼說。你讀過標題以外的內容嗎? – Landon 2008-09-15 23:27:58

回答

106

C和C++表面上很相似,但每個編譯成一組非常不同的代碼。當您使用C++編譯器包含頭文件時,編譯器需要C++代碼。但是,如果它是一個C頭文件,那麼編譯器希望包含在頭文件中的數據被編譯爲某種格式 - C++「ABI」或「應用程序二進制接口」,這樣鏈接器就會窒息。這比將C++數據傳遞給期望C數據的函數更可取。

(要進入真正的事實真相,C++的ABI一般‘軋液’它們的功能/方法的名稱,所以調用printf()毫不氣餒的原型C函數的C++實際上會產生代碼調用_Zprintf,加)

因此:使用extern "C" {...};包括交流標題 - 這很簡單。否則,你在編譯代碼時會出現不匹配,鏈接器會窒息。然而,對於大多數頭文件,您甚至不需要extern,因爲大多數系統C頭文件已經說明了它們可能包含在C++代碼中並且已經代碼爲extern

+1

請您詳細說明**「大多數系統的C頭文件已經說明了這樣一個事實,即它們可能被C++代碼包含,並且已經存在它們的代碼。」**? – 2016-09-28 18:39:52

13

它與不同的編譯器執行名稱修改的方式有關。 C++編譯器將以與C編譯器完全不同的方式來壓縮從頭文件導出的符號的名稱,因此,當您嘗試鏈接時,會出現鏈接器錯誤,指出缺少符號。

爲了解決這個問題,我們告訴C++編譯器以「C」模式運行,所以它會以與C編譯器相同的方式執行名稱修改。完成後,鏈接器錯誤得到修復。

5

這是用來解決名稱修改問題。 extern C意味着這些函數處於「扁平」C風格的API中。

6

C++編譯器創建的符號名稱與C編譯器不同。因此,如果您試圖調用駐留在C文件中的函數(編譯爲C代碼),則需要告訴C++編譯器它正在嘗試解析的符號名稱與默認的符號名稱不同;否則鏈接步驟將失敗。

18

在C++中,可以有不同的實體共享一個名稱。例如,下面是功能列表中的所有命名

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

爲了所有這些,C++編譯器之間的區別將在稱爲名稱 - 裝飾或裝飾的過程中爲每個名稱創建獨特的名稱。 C編譯器不這樣做。而且,每個C++編譯器都可以做到這一點,但方式不同。

extern「C」告訴C++編譯器不要對花括號內的代碼執行任何名稱修改。這允許你從C++中調用C函數。

10

C和C++對符號的名稱有不同的規則。符號是鏈接器如何知道編譯器生成的一個目標文件中的函數「openBankAccount」的調用是對另一個由不同源文件通過相同(或兼容)文件生成的另一個目標文件中稱爲「openBankAccount」的函數的引用。編譯器。這使您可以從多個源文件中創建程序,這對於處理大型項目是一種解脫。

在C中,規則非常簡單,無論如何,符號都在單個名稱空間中。所以整數「socks」存儲爲「socks」,而函數count_socks存儲爲「count_socks」。

鏈接器是爲C語言和C語言等其他語言構建的,具有這種簡單的符號命名規則。所以鏈接器中的符號只是簡單的字符串。

但是在C++中,語言讓你擁有命名空間,多態以及與這樣一個簡單規則相沖突的各種其他事物。所有稱爲「add」的六個多態函數都需要具有不同的符號,否則錯誤的函數將被其他目標文件使用。這是通過「搗毀」(這是一個技術術語)符號的名稱來完成的。當將C++代碼鏈接到C庫或代碼時,需要使用C語言編寫的任何東西(例如C庫的頭文件)的外部「C」來告訴C++編譯器這些符號名稱不會被損壞,而其餘的C++代碼當然必須被破壞或不起作用。

10

我們什麼時候應該使用它?

當你連接C libaries到C++對象文件

什麼是在要求我們 使用它 編譯器/連接水平發生了什麼?

C和C++使用不同的符號命名方案。這告訴鏈接器在給定庫中鏈接時使用C的方案。

如何在編制方面/聯 這是否解決了這 要求我們使用它的問題?

使用C命名方案可以引用C樣式的符號。否則,鏈接器會嘗試不起作用的C++風格的符號。

5

extern "C" {}構造函數指示編譯器不要對花括號中聲明的名稱執行修改。通常,C++編譯器會「增強」函數名稱,以便對參數和返回值的類型信息進行編碼;這被稱爲損壞的名稱extern "C"構造可防止損壞。

它通常在C++代碼需要調用C語言庫時使用。它也可以在將C++函數(例如,從DLL)公開給C客戶端時使用。

104

extern「C」確定應如何命名生成的對象文件中的符號。如果一個函數聲明爲不帶外部「C」,那麼目標文件中的符號名將使用C++名稱修飾。這是一個例子。

鑑於TEST.C像這樣:

void foo() { } 

編譯並在目標文件中列出符號給出:

$ g++ -c test.C 
$ nm test.o 
0000000000000000 T _Z3foov 
       U __gxx_personality_v0 

foo的功能實際上是所謂的 「_Z3foov」。該字符串包含返回類型和參數的類型信息等等。如果你不是寫TEST.C這樣的:

extern "C" { 
    void foo() { } 
} 

然後編譯看看符號:

$ g++ -c test.C 
$ nm test.o 
       U __gxx_personality_v0 
0000000000000000 T foo 

你得到C鏈接。對象文件中的「foo」函數的名稱只是「foo」,並沒有來自名稱修飾的所有花式類型信息。

如果與它一起編譯的代碼是用C編譯器編譯的,但您試圖從C++調用它,那麼通常在extern「C」{}中包含一個頭文件。當你這樣做時,你告訴編譯器頭中的所有聲明都將使用C鏈接。當你鏈接你的代碼時,你的.o文件將包含對「foo」的引用,而不是「_Z3fooblah」,它有望匹配你連接的庫中的任何內容。

大多數現代化的圖書館都會在這些標題周圍放置警衛,以便符號可以通過正確的鏈接進行聲明。例如在很多標準的頭,你會發現:

#ifdef __cplusplus 
extern "C" { 
#endif 

... declarations ... 

#ifdef __cplusplus 
} 
#endif 

這可以確保當C++代碼包括頭,在你的目標文件匹配什麼是在C庫中的符號。你只需要在你的C頭部附近放置extern「C」{},如果它是舊的,並且已經沒有這些防護。

6

只要包含一個頭文件,就可以隨時使用extern「C」,該頭文件定義駐留在由C編譯器編譯的文件中的函數,用於C++文件。 (許多標準的C庫可能會在其標題中包含此檢查以便開發人員更簡單)

例如,如果您有一個包含3個文件的項目,util.c,util.h和main.cpp以及兩者.c和.cpp文件用C++編譯器(g ++,cc等)編譯,那麼它不是真的需要,甚至可能導致鏈接器錯誤。如果您的構建過程對util.c使用常規C編譯器,那麼在包含util.h時將需要使用extern「C」。

發生了什麼事情是C++在其名稱中編碼函數的參數。這是函數重載的工作原理。所有傾向於發生在C函數中的是在名稱的開頭添加下劃線(「_」)。如果不使用extern「C」,當函數的實際名稱是_DoSomething()或者僅僅是DoSomething()時,鏈接器將尋找名爲DoSomething @@ int @ float()的函數。

使用extern「C」通過告訴C++編譯器它應該查找一個遵循C命名約定而不是C++的函數來解決上述問題。

相關問題