混合使用C和C++


最近在看項目代碼,經常看到header file中,開頭:

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

結尾:

#ifdef __cplusplus
}
#endif /* __cplusplus */

中間包裹: includestypedefs,以及function prototypes
不懂就要問,去查了下,發現這是爲了告知編譯器,以C語言的方式編譯代碼。extern "C"表示編譯生成的內部符號名使用C約定。

總述

  • C語言和C++語言現在分家了,有些C語言的功能是C++不具備的。所以最好的方式就是C代碼以C編譯,C++代碼以C++編譯。1
  • extern "C"並不真的改變編譯器讀取代碼的方式。如果你的代碼在.c文件中,那麼編譯器就以C方式編譯,如果在.cpp文件中就以C++方式編譯,除非你對編譯器做了什麼奇怪配置。
  • extern "C"影響的是鏈接過程。C++支持函數重載,而C不支持,兩者的編譯規則也不一樣。函數被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函數的原型爲: void foo( int x, int y ); 該函數被C編譯器編譯後在符號庫中的名字可能爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不 同,但是都採用了相同的機制,生成的新名字稱爲“mangled name”)。2關於具體的生成名字可以通過nm命令來查看,如:nm -a foo.o。關於nm命令自行搜索。
  • extern "C"包裹的代碼仍然是C++代碼。在這個包裹的代碼塊中你能做什麼是有限制的,但只是發生在鏈接過程。你不能定義不能在C鏈接過程中定義的符號(symbols),例如:classes、templates。
    也存在extern "C++",但是最好別嵌套兩者。

問題

不過在StackOverflow上有人提問了幾個挺好的問題。3

  1. 如果C++頭文件A.hh,include 一個C頭文件B.h,B.h中include C.h,這是如何工作的?提問者認爲:編譯器進入B.h時,__cplusplus已經定義過了,所以會包裹上extern "C",這樣就相當於取消定義了__cplusplus,但是進入C.h時,這些將不會被包裹入extern "C"
    【答】 __cplusplus仍存在於extern "C"塊中,但是不影響。
  2. extern "C" { extern "C" { .. } }這樣的代碼是錯誤的嗎? 第二個extern "C"做了什麼?
    【答】 嵌套使用是可以的。__cplusplus在使用C++編譯器時是定義在每一個編譯單元的。通常,這意味着__cplusplus存在於.cpp文件和任何被.cpp文件include的文件。如果不同的編譯但與包含了同一個.h/.hh/.hpp文件可以,那麼在不同時期它們會被解釋爲C或C++代碼。如果你想.h文件中的原型被解譯爲C的符號(symbols),那麼它們應該在C中沒有extern "C",在C++中則有extern "C",所以需要#ifdef __cplusplus檢查。
  3. 我們只是使用在了.h文件中,而非.c文件中。那麼如果一個沒有原型的函數會被編譯器認爲是C++的函數嗎?
    【答】 .cpp文件中,沒有原型並且不在extern "C"塊中的函數,將會由C++鏈接。因爲它們沒有原型,它們只能在本文件中調用,通常你也無須關心鏈接過程,所以這就可以了。
  4. 提問者也在使用一些第三方的C代碼,並且沒有使用extern "C"包裹,那麼每次include一個頭文件,都自己使用extern "C"包裹起來是正確的處理方式嗎?
    【答】 你的處理方法是正確的。include一個需要C鏈接過程的頭文件,你必須使用extern "C"包含這個頭文件,這樣你才能和庫文件鏈接。
  5. 最後,混合使用C和C++還需要注意什麼?
    【答】 沒什麼了。可以這樣混合使用C和C++。

使用

常見方式

當我們想從C++中調用C的庫時,不能僅僅說明是一個外部函數,因爲調用C函數的編譯代碼和調用C++函數的編譯代碼是不同的。如果你僅說明一個外部函數, C++編譯器假定它是C++的函數,編譯成功了,但當你連接時會發現錯誤。解決的方法就是指定它爲C函數:

extern "C" // 後跟函數描述

指定一羣函數的話:

extern "C"{
// 函數描述
}

如果想C和C++混用的話:

#ifdef __cplusplus
extern "C"{
#endif
// 函數描述
#ifdef __cplusplus
}
#endif

參考1推薦方式

參考11認爲上述方式在header file中太醜陋了,所以它推薦把extern "C"用在.cpp文件中。

//main.cpp
extern "C" {
  #include "foo.h"
}
int main() {
  foo(22);
}

這樣不會在.h文件中出現多次嵌套,影響美觀。。。而且不會出現忘記包裹而出現鏈接錯誤。

Legacy

這兩個是我還沒看呢。

  1. include c hdrs system | Standard C++
  2. Call a C function from C++ code

參考


  1. Calling C Code from C++ With ‘extern “C”‘ ↩︎ ↩︎

  2. C++中的Name Mangling ↩︎

  3. Combining C++ and C - how does #ifdef __cplusplus work?
    ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章