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

dub*_*lev 293 c c++ c-preprocessor extern-c

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

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

#ifdef __cplusplus
extern "C" {
#endif
Run Code Online (Sandbox Code Playgroud)

在底部,我们写

#ifdef __cplusplus
}
#endif
Run Code Online (Sandbox Code Playgroud)

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

  1. 如果我有一个C++文件A.hh,其中包含一个C头文件Bh,包含另一个C头文件Ch,这是如何工作的?我认为当编译器进入Bh时, __cplusplus将被定义,因此它将包装代码extern "C" (并且__cplusplus不会在此块内定义).因此,当它进入Ch时, __cplusplus将不会定义并且代码将不会被包装 extern "C".它是否正确?

  2. 包装一段代码有什么问题 extern "C" { extern "C" { .. } }吗?第二个会extern "C" 做什么?

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

  4. 我们还使用了一些用C语言编写的第三方代码,并没有这种包装.每当我从该库中包含一个标题时,我就一直extern "C"在使用#include.这是处理这个问题的正确方法吗?

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

And*_*sky 263

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

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

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

extern "C"块很好地嵌套.还有extern "C++",如果你发现自己陷入绝望的内部extern "C"区域,但它不是从清洁的角度这样一个好主意.

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

关于#1:应在extern "C"块内定义__cplusplus .然而,这并不重要,因为块应该整齐地嵌套.

关于#2:将为正在通过C++编译器运行的任何编译单元定义__cplusplus.通常,这意味着.cpp文件和该.cpp文件包含的任何文件.如果不同的编译单元包含它们,则相同的.h(或.hh或.hpp或what-have-you)可以在不同的时间被解释为C或C++.如果您希望.h文件中的原型引用C符号名称,那么它们extern "C"在被解释为C++时必须具有,并且extern "C"在被解释为C时它们不应该具有- 因此#ifdef __cplusplus检查.

回答你的问题#3:没有原型的函数如果在.cpp文件中而不在extern "C"块内,则会有C++链接.但这很好,因为如果它没有原型,它只能被同一文件中的其他函数调用,然后你通常不关心链接是什么样的,因为你没有计划有这个功能无论如何,被同一个编译单元之外的任何东西调用.

对于#4,你已经完全掌握了它.如果要包含具有C链接的代码的标题(例如由C编译器编译的代码),则必须extern "C"使用标题 - 这样您就可以与库链接.(否则,您的链接器将寻找具有_Z1hic您所寻找的名称的函数void h(int, char)

5:这种混合是一种常见的使用原因extern "C",我认为这样做并没有任何问题 - 只要确保你理解你在做什么.

  • 很好地提到`extern"C++"`当你的C++头文件/代码深陷在某些C代码中时 (10认同)
  • 虽然您应该能够定义(几乎)任何您想要的东西,但 `__cplusplus` 的重点是确定使用的是 `C++` 还是 `C`,因此手动/明确定义它违背了它的目的...... (2认同)

Ant*_*ams 37

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

  2. 你可以extern "C"非常愉快地筑巢.

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

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

  • 如果将 `.c` 文件编译为 C++,那么所有内容都会编译为 C++ 代码,即使它位于 `extern "C"` 块中。`extern "C"` 代码不能使用依赖于 C++ 调用约定的功能(例如运算符重载),但函数体仍然编译为 C++,以及所有需要的功能。 (3认同)

And*_*ent 20

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

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

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

因为你的函数会出现无法找到,如果你做了链接错误将在以后发生具备外部的"C"周围的声明包装和头被包含在C和C++源的混合物.

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

  • 这就是我的头发被拔掉的神秘原因。确实需要张贴在某个地方。 (2认同)

小智 7

这是关于 ABI 的,为了让 C 和 C++ 应用程序能够毫无问题地使用 C 接口。

由于C语言非常简单,对于不同的编译器(例如GCC、Borland C\C++、MSVC等),代码生成多年来都是稳定的。

虽然 C++ 变得越来越流行,但必须在新的 C++ 领域中添加很多东西(例如,最终 Cfront 被 AT&T 放弃,因为 C 无法涵盖它所需的所有功能)。例如模板功能、编译时代码生成等,从过去来看,不同的编译器供应商实际上分别对C++编译器和链接器进行了实际实现,实际的ABI与不同平台上的C++程序根本不兼容。

人们可能仍然喜欢用 C++ 实现实际的程序,但仍然像往常一样保留旧的 C 接口和 ABI,头文件必须声明extern "C" {},它告诉编译器生成兼容/旧/简单/容易的 C ABI对于接口函数,如果编译器是 C 编译器而不是 C++ 编译器。