结合C ++和C - #ifdef __cplusplus如何工作?

时间:2010-09-24 17:03:28

标签: c++ c c-preprocessor extern-c

我正在开发一个包含大量遗留 C 代码的项目。我们已经开始用C ++编写,目的是最终转换遗留代码。我对 C 和C ++的交互方式有点困惑。我知道通过用extern "C"包装 C 代码,C ++编译器不会破坏 C 代码的名称,但我不完全确定如何实现它

所以,在每个 C 头文件的顶部(在包含警卫之后),我们有

#ifdef __cplusplus
extern "C" {
#endif

在底部,我们写

#ifdef __cplusplus
}
#endif

在两者之间,我们拥有所有的includes,typedef和函数原型。我有几个问题,看看我是否正确理解这一点:

  1. 如果我有一个C ++文件A.hh 包含 C 头文件B.h, 包括另一个 C 头文件C.h, 这是如何运作的?我觉得 当编译器进入B.h时, __cplusplus将被定义,所以它 将使用extern "C"包装代码 (而__cplusplus将不会 在这个块里面定义)。所以, 当它进入C.h时, __cplusplus将不会被定义 并且代码不会被包装 extern "C"。这是对的吗?

  2. 有什么不对吗? 用一段代码包装 extern "C" { extern "C" { .. } }? 第二个extern "C"会是什么 办?

  3. 我们不会将此包装器放在.c文件周围,只包含.h文件。那么,如果函数没有原型会发生什么?编译器是否认为它是C ++函数?

  4. 我们也在使用某些第三方 代码用 C 编写,并且确实如此 没有这种包装 它。任何时候我都包含一个标题 从那个图书馆,我一直在推 #include周围的extern "C"。 这是正确的处理方式吗? 是什么?

  5. 最后,这是一个好主意吗? 还有什么我们应该做的吗? 我们将混合 C 和C ++ 在可预见的未来,我 我想确保我们覆盖所有人 我们的基地。

4 个答案:

答案 0 :(得分:249)

extern "C"并没有真正改变编译器读取代码的方式。如果你的代码在.c文件中,它将被编译为C,如果它在.cpp文件中,它将被编译为C ++(除非你对你的配置做了一些奇怪的事情)。

extern "C"的作用是影响联系。编译时,C ++函数的名称受损 - 这就是使重载成为可能的原因。函数名称根据参数的类型和数量进行修改,因此具有相同名称的两个函数将具有不同的符号名称。

extern "C"内的代码仍然是C ++代码。你可以在extern“C”块中做些什么,但它们都是关于链接的。您无法定义任何无法使用C链接构建的新符号。这意味着没有类或模板,例如。

extern "C"很好地嵌套了。如果你发现自己被绝望地困在extern "C++"区域内,还有extern "C",但从清洁角度来看,这不是一个好主意。

现在,特别是有关您编号的问题:

关于#1:__ cplusplus将在extern "C"块内保持定义。但这并不重要,因为块应该整齐地嵌套。

关于#2:将为通过C ++编译器运行的任何编译单元定义__cplusplus。通常,这意味着.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"标题 - 这样您就可以与库链接。 (否则,当您查找_Z1hic时,您的链接器将查找名称为void h(int, char)的函数

5:这种混合是使用extern "C"的常见原因,我认为这样做没有任何问题 - 只要确保你明白自己在做什么。

答案 1 :(得分:37)

  1. extern "C"不会更改__cplusplus宏的存在与否。它只是更改包装声明的链接和名称修改。

  2. 您可以非常愉快地嵌套extern "C"块。

  3. 如果您将.c文件编译为C ++,则不在extern "C"块中且没有extern "C"原型的任何内容都将被视为C ++函数。如果你把它们编译为C,那么当然一切都将是C函数。

  4. 您可以通过这种方式安全地混合C和C ++。

答案 2 :(得分:20)

安德鲁·谢兰斯基(Andrew Shelansky)的完美答案以及与不同意的一些问题并没有真正改变编译器读取代码的方式

因为您的函数原型被编译为C,所以您不能使用不同的参数重载相同的函数名称 - 这是编译器名称修改的关键特性之一。它被描述为一个链接问题,但事实并非如此 - 您将从编译器和链接器中获得错误。

如果您尝试使用原型声明的C ++功能(例如重载),则会出现编译器错误。

链接器错误将在稍后发生,因为如果您 extern" C" 包装器,您的功能似乎无法找到声明和标题包含在C和C ++源代码的混合中。

阻止人们使用编译C作为C ++ 设置的一个原因是因为这意味着他们的源代码不再可移植。该设置是项目设置,因此如果.c文件被放入另一个项目,它将不会被编译为c ++。我宁愿人们花时间将文件后缀重命名为.cpp。

答案 3 :(得分:2)

这与ABI有关,以便让C和C ++应用程序都可以毫无问题地使用C接口。

由于C语言非常容易,因此对于不同的编译器(例如GCC,Borland C \ C ++,MSVC等),代码生成已稳定多年。

尽管C ++越来越流行,但必须在新的C ++域中添加很多东西(例如,最终Cfront在AT&T被放弃了,因为C无法满足其所需的所有功能)。过去,例如 template 功能和编译时代码生成,不同的编译器供应商实际上分别完成了C ++编译器和链接器的实际实现,因此实际的ABI与C ++根本不兼容。程序在不同平台上进行。

人们可能仍然想用C ++实现实际程序,但仍然照旧保留旧的C接口和ABI,头文件必须声明 extern“ C” {} ,它告诉编译器如果编译器是C编译器而不是C ++编译器,则为接口功能生成兼容/旧/简单/简单的C ABI

相关问题