假设我有三个编译对象,全部由相同的编译器/版本生成:
为简单起见,我们假设所有头文件都是用C ++ 11编写的,只使用其语义在所有三个标准版本之间没有改变的构造,因此任何相互依赖关系都是用包含头文件正确表达的并且编译器没有反对。
这些对象的组合是什么,链接到单个二进制文件是不安全的?为什么呢?
编辑:欢迎涵盖主要编制者(例如gcc,clang,vs ++)的答案
答案 0 :(得分:61)
这些对象的组合是什么,链接到单个二进制文件是不安全的?为什么呢?
对于GCC ,将对象A,B和C的任意组合链接在一起是安全的。如果它们都使用相同的版本构建,那么它们是ABI兼容的,标准版本(即-std
选项)没有任何区别。
为什么呢?因为这是我们实施的重要特性,我们努力确保。
如果您遇到问题,如果您将使用不同版本的GCC 和编译的对象链接在一起,那么在GCC对该标准的支持完成之前,您已使用新C ++标准中的不稳定功能。例如,如果使用GCC 4.9和-std=c++11
以及使用GCC 5和-std=c++11
的另一个对象编译对象,则会出现问题。 C ++ 11支持在GCC 4.x中进行了实验,因此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
-std=c++11
-std=c++17
这是因为C ++ 03支持在所有使用的三个编译器版本中都是稳定的,因此C ++ 03组件在所有对象之间是兼容的。自GCC 5以来,C ++ 11支持是稳定的,但是对象D不使用任何C ++ 11特性,对象E和F都使用C ++ 11支持稳定的版本。 C ++ 17支持在任何使用过的编译器版本中都不稳定,但只有对象F使用C ++ 17特性,因此其他两个对象没有兼容性问题(它们共享的唯一功能来自C ++ 03)或C ++ 11,并使用的版本使这些部分OK)。如果您以后想要使用GCC 8和-std=c++17
编译第四个对象G,那么您需要使用相同的版本重新编译F(或者不链接到F),因为F和G中的C ++ 17符号是不相容的。
上述D,E和F之间兼容性的唯一警告是您的程序必须使用GCC 7(或更高版本)中的libstdc++.so
共享库。因为对象F是使用GCC 7编译的,所以您需要使用该版本中的共享库,因为使用GCC 7编译程序的任何部分可能会引入GCC 4.9中libstdc++.so
中不存在的符号的依赖关系或GCC 5.同样,如果您链接到使用GCC 8构建的对象G,则需要使用GCC 8中的libstdc++.so
来确保找到G所需的所有符号。简单的规则是确保程序在运行时使用的共享库至少与用于编译任何对象的版本一样新。
使用GCC时的另一个警告是,在你的问题的评论中已经提到过,自从GCC 5以来,libstdc ++中有两个std::string
的实现。这两个实现不是链接兼容的(它们具有不同的错位名称,因此不能链接在一起)但可以共存于同一个二进制文件中(它们具有不同的错位名称,因此如果一个对象使用{{{},则不要发生冲突1}}和另一个使用std::string
)。如果您的对象使用std::__cxx11::string
,那么通常它们都应该使用相同的字符串实现进行编译。使用std::string
进行编译以选择原始-D_GLIBCXX_USE_CXX11_ABI=0
实现,或gcc4-compatible
选择新的-D_GLIBCXX_USE_CXX11_ABI=1
实现(不要被名称欺骗,它可以在C中使用++ 03,它被称为cxx11
,因为它符合C ++ 11的要求)。默认情况下,哪种实现取决于GCC的配置方式,但在编译时始终可以使用宏覆盖默认值。
答案 1 :(得分:9)
答案分为两部分。编译器级别的兼容性和链接器级别的兼容性。让我们从前者开始。
让我们假设所有标题都是用C ++ 11编写的
使用相同的编译器意味着无论目标C ++标准如何,都将使用相同的标准库头文件和源文件(与编译器相关联的onces)。因此,标准库的头文件被编写为与编译器支持的所有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) };
考虑两个包含头文件的转换单元,但分别是目标C ++ 11和C ++ 14。在定位C ++ 11时,引号中的逗号不被视为参数分隔符;只有一次参数。因此,代码将等同于:
int x[2] = { 0 }; // C++11
另一方面,当定位C ++ 14时,单引号被解释为数字分隔符。因此,代码将等同于:
int x[2] = { 34, 0 }; // C++14 and C++17
这里的要点是在一个纯C ++ 11头文件中使用单引号会导致以C ++ 14/17为目标的翻译单元中出现令人惊讶的错误。因此,即使头文件是用C ++ 11编写的,也必须仔细编写,以确保它与标准的更高版本兼容。 __cplusplus
指令在这里可能很有用。
标准中的其他三个条款包括:
C.3.2第3条:基本概念
更改:新的常规(非展示位置)解除分配器
基本原理:大小取消分配所必需的。
对原始功能的影响:有效的C ++ 2011代码可以声明全局展示位置分配函数和释放函数,如下所示:
void operator new(std::size_t, std::size_t); void operator delete(void*, std::size_t) noexcept;
然而,在本国际标准中,运营商的声明 delete可能与预定义的常规(非放置)运算符删除匹配 (3.7.4)。如果是这样,那么该计划就像集体成员那样格式不正确 分配函数和释放函数(5.3.4)。
C.3.3第7条:声明
更改:constexpr非静态成员函数不是隐式const 成员职能。
基本原理:必须允许constexpr成员函数改变 宾语。
对原始功能的影响:有效的C ++ 2011代码可能无法在此编译 国际标准。
例如,以下代码在C ++ 2011中有效,但在 本国际标准因为它宣布了同一个成员 函数两次使用不同的返回类型:
struct S { constexpr const int &f(); int &f(); };
C.3.4第27条:输入/输出库
更改:未定义获取。
理由:使用获取被视为危险。
对原始功能的影响:使用gets的有效C ++ 2011代码 函数可能无法在本国际标准中编译。
C.4中讨论了C ++ 14和C ++ 17之间潜在的不兼容性。由于所有非标准头文件都是用C ++ 11编写的(如问题中所述),因此不会出现这些问题,所以我不会在这里提及它们。
现在我将讨论链接器级别的兼容性。一般而言,不兼容的潜在原因包括:
main
入口点。如果生成的目标文件的格式取决于目标C ++标准,则链接器必须能够链接不同的目标文件。在GCC,LLVM和VC ++中,幸运的是并非如此。也就是说,对象文件的格式与目标标准无关,尽管它高度依赖于编译器本身。事实上,GCC,LLVM和VC ++的链接器都不需要有关目标C ++标准的知识。这也意味着我们可以链接已编译的目标文件(静态链接运行时)。
如果程序启动例程(调用main
的函数)对于不同的C ++标准不同,并且不同的例程彼此不兼容,则无法链接目标文件。在GCC,LLVM和VC ++中,幸运的是并非如此。此外,main
函数的签名(以及适用于它的限制,参见标准的第3.6节)在所有C ++标准中都是相同的,因此在哪个翻译单元中它并不重要存在。
通常,WPO可能无法与使用不同C ++标准编译的目标文件一起使用。这取决于编译器的确切哪些阶段需要了解目标标准以及哪些阶段不会及其对跨目标文件的过程间优化的影响。幸运的是,GCC,LLVM和VC ++设计得很好而且没有这个问题(不是我所知道的)。
因此,GCC,LLVM和VC ++旨在实现跨不同版本的C ++标准的二进制兼容性。但这并不是标准本身的要求。
顺便说一句,尽管VC ++编译器提供了std switch,它使您能够定位特定版本的C ++标准,但它不支持以C ++ 11为目标。可以指定的最低版本是C ++ 14,它是从Visual C ++ 2013 Update 3开始的默认版本。您可以使用旧版本的VC ++来定位C ++ 11,但是您必须使用不同的VC ++编译器编译针对不同版本的C ++标准的不同翻译单元,这至少会打破WPO。
CAVEAT:我的回答可能不完整或非常精确。
答案 2 :(得分:2)
新的C ++标准分为两部分:语言功能和标准库组件。
正如您所说的新标准,语言本身的变化(例如,ranged-for)几乎没有问题(有时在第三方库标题中存在冲突,并且具有较新的标准语言功能)。
但标准库......
每个编译器版本都附带了C ++标准库(带有gcc的libstdc ++,带有clang的libc ++,带有VC ++的MS C ++标准库,......)的实现,以及一个实现,每个标准版本的实现并不多。在某些情况下,您可以使用标准库的其他实现而不是编译器提供的。您应该关注的是将较旧的标准库实现与较新的标准库实现相关联。
第三方库与您的代码之间可能发生的冲突是链接到第三方库的标准库(和其他库)。