为什么库API +编译器ABI足以确保具有不同版本的gcc的对象之间的兼容性?

时间:2018-08-22 23:14:19

标签: c++ gcc stl abi

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

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

  • 与此问题相关的大多数关注和大部分文章都涉及.so文件和.dll文件之间的交叉兼容性。由于不同的编译器ABI,这非常困难。

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

我的问题与它在计算机级别的工作方式有关。似乎gcc可以更改实现std::string的标头,即使没有更改库API,也可以使其更有效或出于其他原因。如果是这样,那么将使用两个不同的std::string标头来编译两个不同的代码段,并且基本上用相同的名称定义两个不同的类。当我们将使用一个标头的代码中的std::string传递给使用另一个标头的代码时,如何保证对象不会被扭曲或以某种方式误读?

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

// 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的定义为std::string,该定义来自version_b中的标头。但是X().f()产生的对象依赖于使用gcc的version_a头文件的副本编译的机器代码。

我对编译器,链接器和机器指令的底层机制不太了解。但是在我看来,我们似乎在这里打破了一条基本规则,那就是每次使用类时,类的定义都必须相同,否则,我们不能保证上述方案会起作用。 >

编辑:我认为让我感到困惑的主要解决方案是,在这种情况下,“库API”一词的含义比使用“ API”一词的含义更为笼统我习惯于。 gcc文档似乎以一种非常模糊的方式表明,对实现标准库的包含文件的任何更改都可以视为库API的更改。有关详细信息,请参见有关Mohan答案的评论中的讨论。

2 个答案:

答案 0 :(得分:4)

  

gcc似乎可以更改实现std :: string的标头

它不能进行任意更改。这(您猜想)会破坏事情。但是,仅对std::string进行的某些更改会影响该类的内存布局,而这些更改才是重要的。

以不影响内存布局的优化为例:他们可以更改内部的代码

size_t string::find (const string& str, size_t pos = 0) const;

使用更有效的算法。那不会改变字符串的内存布局。

实际上,如果您暂时忽略所有内容都是模板化的事实,因此必须放在头文件中,您可以想象string是在.h中定义的文件,并在.cpp文件中实现。 仅根据头文件的内容确定内存布局。 .cpp文件中的任何内容都可以安全地更改。

他们无法执行的操作示例是向字符串添加新的数据成员。那肯定会破坏事情。

您提到了双重ABI案。那里发生的事情是他们需要进行重大更改,因此他们不得不引入一个新的字符串类。其中一个类是std :: string,另一个是std :: __ cxx11 :: string。 (杂乱的事情在幕后发生,因此大多数用户没有意识到他们在较新版本的编译器/标准库上使用std :: _ cxx11 :: string。)

答案 1 :(得分:4)

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兼容的二进制文件(与不具有兼容性标志的较新二进制文件不兼容)。除非绝对必要,否则不建议使用它。