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

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

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

#ifdef __cplusplus
extern "C" {
#endif

在底部,我们写上

#ifdef __cplusplus
}
#endif

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

  1. 如果我有一个c++文件A.hh 包含C头文件B.h, 包含另一个C头文件C.h, 这是如何工作的呢?我认为 当编译器进入B.h, __cplusplus将被定义,因此它 将用extern "C" (而__cplusplus将不会 在此块中定义)。所以, 当它进入C.h, __cplusplus将不会被定义 代码不会被包装 extern "C"。这对吗?李< / p > < / >

  2. 有什么问题吗 用。包装一段代码 extern "C" { extern "C" { .. } }吗? 第二个extern "C"会是什么 做什么?李< / p > < / >

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

  4. 我们也在使用一些第三方 用C编写的代码 没有这样的包装 它。任何时候我包含一个头文件 图书馆里,我一直在放 #include周围的extern "C"。 这是正确的处理方式吗 了吗?李< / p > < / >
  5. 最后,这个设置是个好主意吗? 我们还需要做些什么吗? 我们将混合使用C和c++ 在可预见的未来,而我 我想确保我们都涵盖了 李我们基地。< / p > < / >

274856 次浏览
  1. extern "C"不会改变__cplusplus宏的存在与否。它只是改变了包装声明的链接和名称分解。

  2. 你可以很高兴地嵌套extern "C"块。

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

  4. 是的

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

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:__cplusplus将为正在通过c++编译器运行的任何编译单元定义。通常,这意味着.cpp文件以及该.cpp文件所包含的任何文件。相同的.h(或.hh或.hpp或什么-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"的常见原因,我不认为这样做有什么错——只要确保你明白你在做什么。

Andrew Shelansky的精彩回答中有几个陷阱,并与并没有真正改变编译器读取代码的方式有一点不同

因为你的函数原型是用C语言编译的,所以你不能用不同的参数重载相同的函数名——这是编译器名称混乱的关键特性之一。它被描述为一个链接问题,但这并不完全正确——你会从编译器和链接器都得到错误。

如果你试图使用c++的原型声明特性,如重载,编译器错误将会出现。

如果你使用对声明进行外来的“C”包装,并且头文件包含在C和c++的混合源代码中,链接器错误将在以后发生,因为你的函数将看起来没有被找到。

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

它是关于ABI的,目的是让C和c++应用程序都可以毫无问题地使用C接口。

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

随着c++变得越来越流行,很多东西必须添加到新的c++领域(例如,最终at&t放弃了Cfront,因为C不能涵盖它所需的所有功能)。例如模板特性,以及编译时代码生成,从过去来看,不同的编译器供应商实际上分别实现了c++编译器和链接器,实际的abi与不同平台上的c++程序根本不兼容。

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