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

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

#ifdef __cplusplus
extern "C" {
#endif

在底部,我们写上

#ifdef __cplusplus
}
#endif

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

If I have a C++ file A.hh which includes a C header file B.h, includes another C header file C.h, how does this work? I think that when the compiler steps into B.h, __cplusplus will be defined, so it will wrap the code with extern "C" (and __cplusplus will not be defined inside this block). So, when it steps into C.h, __cplusplus will not be defined and the code will not be wrapped in extern "C". Is this correct? Is there anything wrong with wrapping a piece of code with extern "C" { extern "C" { .. } }? What will the second extern "C" do? We don't put this wrapper around the .c files, just the .h files. So, what happens if a function doesn't have a prototype? Does the compiler think that it's a C++ function? We are also using some third-party code which is written in C, and does not have this sort of wrapper around it. Any time I include a header from that library, I've been putting an extern "C" around the #include. Is this the right way to deal with that? Finally, is this set up a good idea? Is there anything else we should do? We're going to be mixing C and C++ for the foreseeable future, and I want to make sure we're covering all our bases.


当前回答

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

extern C的作用是影响连杆。c++函数在编译时,其名称会被打乱——这就是使重载成为可能的原因。函数名根据形参的类型和数量进行修改,因此两个同名的函数将具有不同的符号名。

外部“C”中的代码仍然是c++代码。在extern“C”块中可以做的事情是有限制的,但它们都是关于链接的。您不能定义任何不能用C链接构建的新符号。例如,这意味着没有类或模板。

extern“C”块巢很好。如果您发现自己无望地困在extern“C”区域中,还可以使用extern“c++”,但从清洁的角度来看,这并不是一个好主意。

现在,具体来说,关于你编号的问题:

关于#1:__cplusplus将在extern“C”块中定义。不过,这并不重要,因为块应该整齐地嵌套。

Regarding #2: __cplusplus will be defined for any compilation unit that is being run through the C++ compiler. Generally, that means .cpp files and any files being included by that .cpp file. The same .h (or .hh or .hpp or what-have-you) could be interpreted as C or C++ at different times, if different compilation units include them. If you want the prototypes in the .h file to refer to C symbol names, then they must have extern "C" when being interpreted as C++, and they should not have extern "C" when being interpreted as C -- hence the #ifdef __cplusplus checking.

回答你的问题#3:如果没有原型的函数在.cpp文件中,而不是在extern“C”块中,那么它们将具有c++链接。不过,这很好,因为如果它没有原型,它只能由同一文件中的其他函数调用,然后您通常不关心链接是什么样子的,因为您不打算让同一编译单元之外的任何东西调用该函数。

对于第4点,你已经完全明白了。如果您包含一个带有C链接的代码头(例如由C编译器编译的代码),那么您必须将标头扩展为“C”——这样您将能够链接到库。(否则,当你在寻找void h(int, char)时,你的链接器会寻找像_Z1hic这样的函数名)

5:这种混合是使用extern“C”的常见原因,我不认为这样做有什么错——只是要确保你明白你在做什么。

其他回答

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

extern C的作用是影响连杆。c++函数在编译时,其名称会被打乱——这就是使重载成为可能的原因。函数名根据形参的类型和数量进行修改,因此两个同名的函数将具有不同的符号名。

外部“C”中的代码仍然是c++代码。在extern“C”块中可以做的事情是有限制的,但它们都是关于链接的。您不能定义任何不能用C链接构建的新符号。例如,这意味着没有类或模板。

extern“C”块巢很好。如果您发现自己无望地困在extern“C”区域中,还可以使用extern“c++”,但从清洁的角度来看,这并不是一个好主意。

现在,具体来说,关于你编号的问题:

关于#1:__cplusplus将在extern“C”块中定义。不过,这并不重要,因为块应该整齐地嵌套。

Regarding #2: __cplusplus will be defined for any compilation unit that is being run through the C++ compiler. Generally, that means .cpp files and any files being included by that .cpp file. The same .h (or .hh or .hpp or what-have-you) could be interpreted as C or C++ at different times, if different compilation units include them. If you want the prototypes in the .h file to refer to C symbol names, then they must have extern "C" when being interpreted as C++, and they should not have extern "C" when being interpreted as C -- hence the #ifdef __cplusplus checking.

回答你的问题#3:如果没有原型的函数在.cpp文件中,而不是在extern“C”块中,那么它们将具有c++链接。不过,这很好,因为如果它没有原型,它只能由同一文件中的其他函数调用,然后您通常不关心链接是什么样子的,因为您不打算让同一编译单元之外的任何东西调用该函数。

对于第4点,你已经完全明白了。如果您包含一个带有C链接的代码头(例如由C编译器编译的代码),那么您必须将标头扩展为“C”——这样您将能够链接到库。(否则,当你在寻找void h(int, char)时,你的链接器会寻找像_Z1hic这样的函数名)

5:这种混合是使用extern“C”的常见原因,我不认为这样做有什么错——只是要确保你明白你在做什么。

extern "C"不会改变__cplusplus宏的存在或不存在。它只是改变了包装声明的链接和名称分解。 你可以很高兴地嵌套“C”块。 如果您将. C文件编译为c++,那么任何不在extern "C"块中的内容,以及没有extern "C"原型的内容都将被视为c++函数。如果你把它们编译成C,那么当然所有的东西都是C函数。 是的 您可以通过这种方式安全地混合使用C和c++。

Andrew Shelansky的回答很好,但有一点不一致并不会真正改变编译器读取代码的方式

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

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

链接器错误将在以后发生,因为如果声明周围没有extern“C”包装器,并且头文件包含在C和c++源代码的混合物中,那么函数将看起来没有被找到。

不鼓励人们使用compile C as 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++编译器。