Jon*_*ely 87
这些对象的组合是什么,链接到单个二进制文件是不安全的?为什么?
对于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编译第四个对象G,-std=c++17
那么您需要使用相同的版本(或不链接到F)重新编译F,因为F和G中的C++ 17符号不兼容.
上述D,E和F之间兼容性的唯一警告是您的程序必须使用libstdc++.so
GCC 7(或更高版本)的共享库.因为对象F是使用GCC 7编译的,所以您需要使用该版本中的共享库,因为使用GCC 7编译程序的任何部分可能会引入对libstdc++.so
来自GCC 4.9或GCC 5 中不存在的符号的依赖性.同样,如果链接到使用GCC 8构建的对象G,则需要使用libstdc++.so
from GCC 8来确保找到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的配置方式,但在编译时始终可以使用宏覆盖默认值.
Had*_*ais 14
答案分为两部分.编译器级别的兼容性和链接器级别的兼容性.让我们从前者开始吧.
我们假设所有头文件都是用C++ 11编写的
使用相同的编译器意味着将使用相同的标准库头文件和源文件(与编译器相关联的onces),而不管目标C++标准如何.因此,标准库的头文件被编写为与编译器支持的所有C++版本兼容.
也就是说,如果用于编译翻译单元的编译器选项指定了特定的C++标准,则不应该访问仅在较新标准中可用的任何功能.这是使用该__cplusplus
指令完成的.请参阅矢量源文件,以获取有关如何使用它的有趣示例.同样,编译器将拒绝较新版本标准提供的任何语法功能.
所有这些意味着您的假设只适用于您编写的头文件.当这些头文件包含在针对不同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) };
Run Code Online (Sandbox Code Playgroud)
考虑两个包含头文件的转换单元,但分别是目标C++ 11和C++ 14.在定位C++ 11时,引号中的逗号不被视为参数分隔符; 只有一次参数.因此,代码相当于:
int x[2] = { 0 }; // C++11
Run Code Online (Sandbox Code Playgroud)
另一方面,当以C++ 14为目标时,单引号被解释为数字分隔符.因此,代码相当于:
int x[2] = { 34, 0 }; // C++14 and C++17
Run Code Online (Sandbox Code Playgroud)
这里的要点是在一个纯C++ 11头文件中使用单引号可能会导致以C++ 14/17为目标的翻译单元中出现令人惊讶的错误.因此,即使头文件是用C++ 11编写的,也必须仔细编写,以确保它与标准的更高版本兼容.该__cplusplus
指令在这里可能很有用.
该标准的其他三个条款包括:
C.3.2第3条:基本概念
更改:新的常规(非放置)解除分配器
基本原理:大小取消分配所必需的.
对原始特性的影响:有效的C++ 2011代码可以声明全局放置分配函数和释放函数,如下所示:
Run Code Online (Sandbox Code Playgroud)void operator new(std::size_t, std::size_t); void operator delete(void*, std::size_t) noexcept;
但是,在本国际标准中,操作员删除声明可能与预定义的常规(非放置)操作员删除(3.7.4)相匹配.如果是这样,程序就会形成错误,就像类成员分配函数和释放函数一样(5.3.4).
C.3.3第7条:声明
更改:constexpr非静态成员函数不是隐式const成员函数.
基本原理:必须允许constexpr成员函数改变对象.
对原始功能的影响:有效的C++ 2011代码可能无法在本国际标准中编译.
例如,以下代码在C++ 2011中有效,但在本国际标准中无效,因为它使用不同的返回类型声明了两次相同的成员函数:
Run Code Online (Sandbox Code Playgroud)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开关,它使您能够定位特定版本的C++标准,但它不支持以C++ 11为目标.可以指定的最低版本是C++ 14,它是从Visual C++ 2013 Update 3开始的默认版本.您可以使用旧版本的VC++来定位C++ 11,但是您必须使用不同的VC++编译器编译针对不同版本的C++标准的不同翻译单元,这至少会打破WPO.
CAVEAT:我的答案可能不完整或非常精确.
归档时间: |
|
查看次数: |
6748 次 |
最近记录: |