链接 C++17、C++14 和 C++11 对象是否安全

2022-01-11 00:00:00 linker c++ c++11 abi c++14

假设我有三个编译对象,全部由相同的编译器/版本生成:

Suppose I have three compiled objects, all produced by the same compiler/version:

  1. A 是使用 C++11 标准编译的
  2. B 使用 C++14 标准编译
  3. C 是使用 C++17 标准编译的

为简单起见,我们假设所有标头都是用 C++11 编写的,仅使用语义在所有三个标准版本之间都没有改变的结构,因此任何相互依赖都可以通过标头包含正确表达并且编译器没有反对.

For simplicity, let's assume all headers were written in C++11, using only constructs whose semantics haven't changed between all three standard versions, and so any interdependencies were correctly expressed with header inclusion and the compiler did not object.

这些对象的哪些组合是,链接到单个二进制文件是否安全?为什么?

Which combinations of these objects is it and isn't it safe to link into a single binary? Why?

欢迎提供涵盖主要编译器(例如 gcc、clang、vs++)的答案

answers covering major compilers (e.g. gcc, clang, vs++) are welcome

推荐答案

这些对象的哪些组合是,链接到单个二进制文件是否安全?为什么?

Which combinations of these objects is it and isn't it safe to link into a single binary? Why?

对于 GCC,将对象 A、B 和 C 的任意组合链接在一起是安全的.如果它们都使用相同的版本构建,那么它们是 ABI 兼容的,即标准版本(即-std 选项)没有任何区别.

For GCC it is safe to link together any combination of objects A, B, and C. If they are all built with the same version then they are ABI compatible, the standard version (i.e. the -std option) doesn't make any difference.

为什么?因为这是我们实现的一个重要属性,我们努力确保这一点.

Why? Because that's an important property of our implementation which we work hard to ensure.

如果您将使用不同版本的 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 支持仍处于试验阶段和发展阶段.

Where you have problems is if you link together objects compiled with different versions of GCC and you have used unstable features from a new C++ standard before GCC's support for that standard is complete. For example, if you compile an object using GCC 4.9 and -std=c++11 and another object with GCC 5 and -std=c++11 you will have problems. The C++11 support was experimental in GCC 4.x, and so there were incompatible changes between the GCC 4.9 and 5 versions of C++11 features. Similarly, if you compile one object with GCC 7 and -std=c++17 and another object with GCC 8 and -std=c++17 you will have problems, because C++17 support in GCC 7 and 8 is still experimental and evolving.

另一方面,以下对象的任意组合都可以使用(尽管请参阅下面关于 libstdc++.so 版本的注释):

On the other hand, any combination of the following objects will work (although see note below about libstdc++.so version):

  • 使用 GCC 4.9 和 -std=c++03
  • 编译的对象 D
  • 使用 GCC 5 和 -std=c++11
  • 编译的对象 E
  • 使用 GCC 7 和 -std=c++17
  • 编译的对象 F
  • object D compiled with GCC 4.9 and -std=c++03
  • object E compiled with GCC 5 and -std=c++11
  • object F compiled with GCC 7 and -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,并且使用的版本使这些部分正常).如果您稍后想使用 GCC 8 和 -std=c++17 编译第四个对象 G,那么您需要使用相同版本(或不链接到 F)重新编译 F,因为F 和 G 中的 C++17 符号不兼容.

This is because C++03 support is stable in all three compiler versions used, and so the C++03 components are compatible between all the objects. C++11 support is stable since GCC 5, but object D doesn't use any C++11 features, and objects E and F both use versions where C++11 support is stable. C++17 support is not stable in any of the used compiler versions, but only object F uses C++17 features and so there is no compatibility issue with the other two objects (the only features they share come from C++03 or C++11, and the versions used make those parts OK). If you later wanted to compile a fourth object, G, using GCC 8 and -std=c++17 then you would need to recompile F with the same version (or not link to F) because the C++17 symbols in F and G are incompatible.

对于上述 D、E 和 F 之间的兼容性,唯一需要注意的是您的程序必须使用 GCC 7(或更高版本)中的 libstdc++.so 共享库.因为对象 F 是使用 GCC 7 编译的,所以您需要使用该版本中的共享库,因为使用 GCC 7 编译程序的任何部分可能会引入对 libstdc++.so 来自 GCC 4.9 或 GCC 5.同样,如果您链??接到使用 GCC 8 构建的对象 G,则需要使用 GCC 8 中的 libstdc++.so 以确保找到 G 所需的所有符号.简单的规则是确保程序在运行时使用的共享库至少与用于编译任何对象的版本一样新.

The only caveat for the compatibility described above between D, E and F is that your program must use the libstdc++.so shared library from GCC 7 (or later). Because object F was compiled with GCC 7, you need to use the shared library from that release, because compiling any part of the program with GCC 7 might introduce dependencies on symbols that are not present in the libstdc++.so from GCC 4.9 or GCC 5. Similarly, if you linked to object G, built with GCC 8, you would need to use the libstdc++.so from GCC 8 to ensure all symbols needed by G are found. The simple rule is to ensure the shared library the program uses at run-time is at least as new as the version used to compile any of the objects.

使用 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 的配置方式,但默认值始终可以在编译时用宏覆盖.

Another caveat when using GCC, already mentioned in the comments on your question, is that since GCC 5 there are two implementations of std::string available in libstdc++. The two implementations are not link-compatible (they have different mangled names, so can't be linked together) but can co-exist in the same binary (they have different mangled names, so don't conflict if one object uses std::string and the other uses std::__cxx11::string). If your objects use std::string then usually they should all be compiled with the same string implementation. Compile with -D_GLIBCXX_USE_CXX11_ABI=0 to select the original gcc4-compatible implementation, or -D_GLIBCXX_USE_CXX11_ABI=1 to select the new cxx11 implementation (don't be fooled by the name, it can be used in C++03 too, it's called cxx11 because it conforms to the C++11 requirements). Which implementation is the default depends on how GCC was configured, but the default can always be overridden at compile-time with the macro.

相关文章