c++ - 为什么library API + compiler ABI就足以保证不同版本gcc对象之间的兼容性?

标签 c++ gcc stl abi

我遇到过这样一种情况,我可能想将使用一个版本的 gcc 编译的 C++ 共享对象库与一些将使用另一个版本的 gcc 编译的代码一起使用。特别是,我想使用返回一些 STL 容器的方法,例如 std::stringstd::map

gcc website许多旧的 stackoverflow 帖子(例如 here)讨论了这个问题。我目前的理解是

  • 关于此问题的大部分关注和帖子都是关于 .so 文件和 .dll 文件之间的交叉兼容性。由于不同的编译器 ABI,这非常困难。

  • 对于使用不同版本的 gcc(至少 gcc 版本 >= 3.4)编译的 .so 文件之间的交叉兼容性,您需要确保标准库 API 没有改变(并且,如果它有,有 dual ABI 支持)。

我的问题与它在机器级别的工作方式有关。看起来 gcc 可能会更改实现 std::string 的 header ,即使库 API 没有更改,以使其更有效或出于其他原因。如果是这样,那么两段不同的代码将使用两个不同的 std::string header 进行编译,并且基本上定义了两个具有相同名称的不同类。我们如何保证,当我们将 std::string 从使用一个 header 的代码传递到使用另一个 header 的代码时,该对象不会以某种方式被损坏或误读?

例如,假设我有以下文件:

// File a.h:

#ifndef FILE_A
#define FILE_A

#include <string>

class X {
  public:
    std::string f();
};

#endif  // FILE_A


// File a.cpp:

#include "a.h"

std::string X::f() {
  return "hello world";
}


// File b.cpp:

#include <iostream>
#include <string>
#include "a.h"

int main() {
  std::string x = X().f();
  std::cout << x << std::endl;
}

(此处类 X 的唯一目的是在我测试其工作原理时向共享对象库中引入更多的名称修改。)

现在我编译这些如下:

/path/to/gcc/version_a/bin/g++ -fPIC -shared a.cpp -o liba.so
/path/to/gcc/version_b/bin/g++ -L. -la -o b b.cpp

当我执行 b 时,b 具有来自 version_b< header 的 std::string 的定义。但是 X().f() 生成的对象依赖于使用来自 gcc version_a 的 header 拷贝编译的机器代码。

我不太了解编译器、链接器和机器指令的低级机制。但在我看来,我们在这里打破了一个基本规则,即类的定义在每次使用时都必须相同,否则,我们无法保证上述方案是否有效。

编辑:我认为解决我的困惑的主要方法是,短语“库 API”在这种情况下比在术语“API”的使用中更普遍我习惯于。 gcc 文档似乎以一种非常模糊的方式表明,几乎所有对实现标准库的包含文件的更改都可以视为库 API 中的更改。有关详细信息,请参阅关于 Mohan 答案的评论中的讨论。

最佳答案

GCC 必须尽一切努力使我们的程序运行。如果在不同的翻译单元中使用 std::string 的不同实现意味着我们的程序被破坏了,那么 gcc 是不允许这样做的。

这适用于任何给定版本的 GCC。

GCC 竭尽全力保持向后兼容。也就是说,它努力使上述内容在不同版本的 GCC 中保持适用,而不仅仅是在给定版本中。然而,它不能保证其所有版本直到永远都将保持兼容。当不再可能保持向后兼容性时,将引入 ABI 更改。

自从 GCC-5 ABI 发生重大变化以来,它以这样一种方式引入:如果您将新旧二进制文件组合在一起,它会试图故意破坏您的构建。它通过在二进制级别重命名 std::stringstd::list 类来实现。这会传播到所有具有 std::stringstd::list 参数的函数和模板。如果你试图通过例如针对不兼容的 ABI 版本编译的翻译单元之间的 std::string,您的程序将无法链接。该机制并非 100% 万无一失,但它可以捕获许多常见情况。

另一种方法是默默地生成损坏的可执行文件,这是没人想要的。

双 ABI 是新版本的 GCC 标准库 binary 与旧版可执行文件保持兼容的一种方式。基本上它有两个版本涉及 std::stringstd::list,链接器有不同的符号名称,所以使用旧版本的旧程序名称仍然可以加载和运行。

还有一个编译标志允许较新版本的 GCC 生成与旧 ABI 兼容的二进制文件(并且与没有兼容标志生成的较新二进制文件不兼容)。除非绝对必要,否则不建议使用它。

关于c++ - 为什么library API + compiler ABI就足以保证不同版本gcc对象之间的兼容性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51976376/

相关文章:

c++ - GCC 在 lambda 函数中通过引用错误地捕获全局变量?

c - 为什么GCC在编译C代码时不使用更多寄存器

C++:我应该通过数组实现堆栈/队列/双端队列以提高性能吗?

c++ - 为什么 std::find() 不使用我的 operator==?

c++ - union 可以包含具有用户定义构造函数的类的对象吗?

c++ - `*--p` 在 C++03 中实际上是否合法(格式正确)

c++ - 类型引用的无效初始化

c++ - 将数据从 C# 传递到非托管 C++(使用 COM Interop)

c++ - 在带有斜杠的路径上返回std::filesystem::create_directories()的值

c++ - 从c++中的函数返回数组 vector