混合使用C和C++

  • ​​总述​​
  • ​​问题​​
  • ​​使用​​
  • ​​常见方式​​
  • ​​参考1推荐方式​​
  • ​​Legacy​​
  • ​​参考​​


最近在看项目代码,经常看到header file中,开头:

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

结尾:

#ifdef __cplusplus
}
#endif /* __cplusplus */

中间包裹: ​​includes​​​,​​typedefs​​​,以及​​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推荐方式

参考1​​1​​​认为上述方式在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?
    ​ ↩︎