结合C ++和C – #ifdef __cplusplus如何工作?
我正在做一个有很多遗留C代码的项目。 我们已经开始用C ++编写,并且最终还要转换遗留代码。 我对C和C ++如何交互有点困惑。 我明白,通过用extern "C"
包装C代码,C ++编译器不会破坏C代码的名称,但我不完全确定如何实现这一点。
所以,在每个C头文件的顶部(包括后卫),我们有
#ifdef __cplusplus extern "C" { #endif
在底部,我们写
#ifdef __cplusplus } #endif
在这两者之间,我们拥有所有的包含,typedef和函数原型。 我有几个问题,看看我是否正确理解这一点:
-
如果我有一个包含C头文件Bh的C ++文件A.hh,包含另一个C头文件Ch,这是如何工作的? 我认为,当编译器进入Bh时,
__cplusplus
将被定义,所以它会用extern "C"
来包装代码(并且__cplusplus
不会在这个块内部被定义)。 所以,当它进入Ch时,__cplusplus
将不会被定义,代码也不会被包装在extern "C"
。 它是否正确? -
用
extern "C" { extern "C" { .. } }
包装一段代码有什么不对吗? 第二个extern "C"
做什么? -
我们不把这个包装在.c文件,只是.h文件。 那么,如果一个函数没有原型,会发生什么? 编译器是否认为它是一个C ++函数?
-
我们也使用了一些用C编写的第三方代码,并没有这种包装。 任何时候我从这个库中包含一个头文件,我一直在#include周围放置一个
extern "C"
。 这是对付这个问题的正确方法吗? -
最后,这是一个好主意吗? 还有什么我们该做的? 我们将在可预见的未来混合使用C和C ++,并且要确保我们覆盖了所有的基础。
extern "C"
并不真正改变编译器读取代码的方式。 如果你的代码是在一个.c文件中,它将被编译为C,如果它在.cpp文件中,它将被编译为C ++(除非你对你的configuration做了一些奇怪的事情)。
extern "C"
所做的是影响联动。 C ++函数在编译时,名字被破坏 – 这是使得重载成为可能的原因。 函数名称根据参数的types和数量进行修改,以使两个同名的函数具有不同的符号名称。
extern "C"
代码extern "C"
仍然是C ++代码。 在外部“C”块中可以做什么是有限制的,但是它们都是关于连接的。 您不能定义任何不能用C链接build立的新符号。 这意味着没有类或模板,例如。
extern "C"
块很好地嵌套。 如果你发现自己被困在extern "C"
区域内,还有extern "C++"
,但是从清洁的angular度来看,这不是一个好主意。
现在,特别是关于你编号的问题:
关于#1:应在extern "C"
块内定义__cplusplus。 不过这并不重要,因为这些街区应该整齐地筑巢。
关于#2:__cplusplus将被定义为正在通过C ++编译器运行的任何编译单元。 通常,这意味着.cpp文件和该.cpp文件包含的任何文件。 如果不同的编译单元包含它们,那么相同的.h(或.hh或.hpp或what-have-you)可以在不同的时间被解释为C或C ++。 如果你希望.h文件中的原型引用C符号名称,那么当它们被解释为C ++时,它们必须具有extern "C"
,并且当被解释为C时它们不应该具有extern "C"
– 因此, #ifdef __cplusplus
检查。
回答你的问题#3:没有原型的函数如果在.cpp文件中,并且不在extern "C"
块中,将有C ++链接。 这很好,因为如果它没有原型,它只能被同一个文件中的其他函数调用,然后你通常不关心链接是什么样子的,因为你没有计划使用这个函数无论如何都被同一个编译单元之外的任何东西所调用。
#4,你确切地知道了。 如果包含具有C链接的代码的头文件(例如由C编译器编译的代码),那么必须在头文件的extern "C"
处外接头文件 – 这样您才能够与库链接。 (否则,当你在寻找void h(int, char)
时候,你的链接器将会用_Z1hic
这样的名字寻找函数,
5:这种混合是使用extern "C"
一个常见原因,我没有看到这样做有什么问题 – 只要确保你明白你在做什么。
-
extern "C"
不会改变__cplusplus
macros的存在与否。 它只是改变了包装声明的链接和名称。 -
你可以非常愉快地嵌套
extern "C"
块。 -
如果将
.c
文件编译为C ++,那么任何不在extern "C"
块中,而没有extern "C"
原型将被视为C ++函数。 如果你把它们编译为C,那么当然,所有的东西都是C函数。 -
是
-
你可以这样安全地混合使用C和C ++。
一些陷阱是Andrew Shelansky的出色答案,并且有点不同意, 并没有真正改变编译器读取代码的方式
因为你的函数原型被编译为C,所以你不能用相同的参数重载相同的函数名 – 这是编译器名称重组的一个关键特性。 它被描述为一个链接问题,但是这不是真的 – 你会从编译器和链接器中得到错误。
编译器错误将是如果您尝试使用C ++function的原型声明,如重载。
链接器错误将在稍后发生,因为如果在声明周围没有 外部“C”包装器,并且头文件包含在C和C ++源文件中,您的函数将不会被find。
阻止人们将C编译为C ++设置的一个原因是因为这意味着他们的源代码不再是可移植的。 该设置是一个项目设置,所以如果一个.c文件被放到另一个项目中,它不会被编译为c ++。 我宁愿花时间将文件后缀重命名为.cpp。