- A 使用C++11标准编译
- B 使用C++14标准编译
- C 使用C++17标准编译
哪些组合是安全的可以链接到单个二进制文件中?哪些组合不是?为什么?
注:欢迎涵盖主要编译器(例如gcc、clang、vs++)的答案。
-std
选项)没有影响。-std=c++11
选项的对象,又使用GCC 5编译了另一个带有-std=c++11
选项的对象,你就会遇到问题。GCC 4.x中的C++11支持是实验性的,因此在GCC 4.9和5版本的C++11功能之间存在不兼容的更改。同样,如果你使用GCC 7和-std=c++17
选项编译一个对象,并使用GCC 8和-std=c++17
选项编译另一个对象,你也会遇到问题,因为GCC 7和8中的C++17支持仍处于实验阶段并且在不断发展。libstdc++.so
版本的说明):-std=c++03
选项编译的对象D
- 使用GCC 5和-std=c++11
选项编译的对象E
- 使用GCC 7和-std=c++17
选项编译的对象F这是因为所有三个编译器版本都支持C++03,使得C++03组件在所有对象之间兼容。自GCC 5以来,C++11支持稳定,但对象D没有使用任何C++11功能,而对象E和F都使用了C++11支持稳定的版本。在这些已使用的编译器版本中,C++17支持都不稳定,但只有对象F使用了C++17功能,因此与其他两个对象没有兼容性问题(它们唯一共享的功能来自于C++03或C++11,并且使用的版本可以处理那些部分)。如果您之后想要使用GCC 8和-std=c++17
编译第四个对象G,则需要重新编译带有相同版本的F(或者不链接到F),因为F和G中的C++17符号不兼容。libstdc++.so
共享库。由于对象F是使用GCC 7编译的,因此您需要使用该版本的共享库,因为使用GCC 7编译程序的任何部分可能会引入对GCC 4.9或GCC 5的libstdc++.so
中不存在的符号的依赖项。同样,如果您链接到使用GCC 8构建的对象G,则需要使用GCC 8的libstdc++.so
,以确保找到G所需的所有符号。简单的规则是确保程序在运行时使用的共享库至少与用于编译任何对象的版本一样新。当使用GCC时需要注意一个细节,正如你问题中的评论中提到的,自从GCC 5以来,libstdc++中有两个实现std::string
。这两个实现不兼容(它们有不同的符号名称,因此无法链接在一起),但可以在同一个二进制文件中共存(它们有不同的符号名称,因此如果一个对象使用std::string
,而另一个对象使用std::__cxx11::string
,它们就不会冲突)。如果您的对象使用std::string
,那么通常应该使用相同的字符串实现编译它们。使用-D_GLIBCXX_USE_CXX11_ABI=0
编译选项选择原始的gcc4-compatible
实现,或者使用-D_GLIBCXX_USE_CXX11_ABI=1
编译选项选择新的cxx11
实现(别被名称愚弄了,它也可以用于C++03,它之所以叫cxx11
是因为它符合C++11的要求)。哪个实现是默认的取决于GCC的配置,但默认值可以在编译时通过宏进行覆盖。答案分为两部分:编译器级别的兼容性和链接器级别的兼容性。我们先从前者开始。
假设所有头文件都是用C++11编写的
使用相同的编译器意味着将使用相同的标准库头文件和源文件(与编译器相关的文件),无论目标C++标准如何。因此,标准库的头文件被编写为与编译器支持的所有C++版本兼容。
话虽如此,如果用于编译翻译单元的编译器选项指定了特定的C++标准,则新标准中提供的任何功能都不应该可访问。这是使用__cplusplus
指令完成的。有关如何使用它的有趣示例,请参见vector源文件。同样,编译器将拒绝任何由较新版本的标准提供的语法特性。
所有这些意味着您的假设只适用于您编写的头文件。当这些头文件包含在针对不同C++标准的不同翻译单元中时,这些头文件可能会导致不兼容性。这在C++标准的附录C中进行了讨论。有4个条款,我只讨论第一个,并简要提到其余部分。
C.3.1 条款2:词法约定
在C++11中,单引号用于界定字符字面值,而在C++14和C++17中,它们是数字分隔符。假设您在纯C++11的某个头文件中具有以下宏定义:
#define M(x, ...) __VA_ARGS__
// Maybe defined as a field in a template or a type.
int x[2] = { M(1'2,3'4) };
int x[2] = { 0 }; // C++11
另一方面,当目标为C++14时,单引号被解释为数字分隔符。因此,代码等效于:
int x[2] = { 34, 0 }; // C++14 and C++17
__cplusplus
指令可能很有用。Change: New usual (non-placement) deallocator
Rationale: Required for sized deallocation.
Effect on original feature: Valid C++2011 code could declare a global placement allocation function and deallocation function as follows:
void operator new(std::size_t, std::size_t); void operator delete(void*, std::size_t) noexcept;
In this International Standard, however, the declaration of operator delete might match a predefined usual (non-placement) operator delete (3.7.4). If so, the program is ill-formed, as it was for class member allocation functions and deallocation functions (5.3.4).
C.3.3第7条款:声明
Change: constexpr non-static member functions are not implicitly const member functions.
Rationale: Necessary to allow constexpr member functions to mutate the object.
Effect on original feature: Valid C++2011 code may fail to compile in this International Standard.
For example, the following code is valid in C++2011 but invalid in this International Standard because it declares the same member function twice with different return types:
struct S { constexpr const int &f(); int &f(); };
C.3.4第27条款:输入/输出库
更改:未定义gets。
原理:使用gets被认为是危险的。
对原始功能的影响:使用gets函数的有效C++2011代码可能无法在此国际标准中编译。
C++14和C++17之间的潜在不兼容性在C.4中进行了讨论。由于所有非标准头文件都是用C++11编写的(如问题中所指定的),因此这些问题不会发生,因此我不会在此处提及它们。
现在我将讨论链接器级别的兼容性。一般来说,不兼容性的潜在原因包括以下内容:
main
入口点。如果生成的目标文件格式取决于目标C++标准,则链接器必须能够链接不同的目标文件。在GCC、LLVM和VC++中,幸运的是这并不是问题。也就是说,对象文件的格式与目标标准无关,尽管它高度依赖于编译器本身。实际上,GCC、LLVM和VC++的链接器都不需要了解目标C++标准。这也意味着我们可以链接已经编译的对象文件(静态链接运行时)。
如果程序启动例程(调用main
的函数)在不同的C++标准下不同,并且不兼容,则无法链接目标文件。在GCC、LLVM和VC++中,幸运的是不存在这种情况。此外,main
函数的签名(以及适用于它的限制,请参见标准的第3.6节)在所有C++标准中都相同,因此它存在于哪个翻译单元中并不重要。注意:我的答案可能不完整或不太精确。
std::string
实现与使用的-std
模式无关。这是一个重要的特性,_恰好_支持像OP的情况。你可以在C++03代码中使用新的std::string
,也可以在C++11代码中使用旧的std::string
(请参见Matteo稍后的评论中的链接)。 - Jonathan Wakely