我遇到过一种情况,我可能想要使用一个版本的gcc编译的C++共享对象库,其中一些代码将使用另一个版本的gcc编译.特别是,我想使用返回一些STL容器的方法,如std::string和std::map.
在GCC网站和许多老计算器职位(如这里)讨论这个问题.我目前的理解是
此问题的大多数问题和大多数帖子都是关于.so文件和.dll文件之间的交叉兼容性.由于编译器ABI不同,这非常困难.
对于使用不同版本的gcc(至少使用gcc版本> = 3.4)编译的.so文件之间的交叉兼容性,您需要确保的是标准库API没有更改(如果有,则有双重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;
}
Run Code Online (Sandbox Code Playgroud)
(这个类的唯一目的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
Run Code Online (Sandbox Code Playgroud)
当我执行时b,那么b它的定义std::string来自于标题version_b.但是生成的对象X().f()依赖于使用来自version_agcc 的标头副本编译的机器代码.
我不太了解编译器,链接器和机器指令的低级机制.但在我看来,我们在这里打破了一个基本规则,即每次使用时类的定义必须相同,如果不是,我们无法保证上述情况能够奏效.
编辑:我认为我的困惑的主要解决方案是,"库API"这个短语在这种情况下比在我习惯使用的术语"API"中更为通用.gcc文档似乎以非常模糊的方式表明,对实现标准库的包含文件的任何更改都可以被视为库API中的更改.有关详细信息,请参阅Mohan答案评论中的讨论.
GCC 必须尽一切努力让我们的计划发挥作用。如果在不同的翻译单元中使用不同的实现std::string意味着我们的程序被破坏,那么 gcc 就不允许这样做。
这适用于任何给定版本的 GCC。
GCC 不遗余力地保持向后兼容。也就是说,它力求上述内容仍然适用于不同版本的 GCC,而不仅仅是在给定版本内。然而,它不能保证其所有版本直到永远都将保持兼容。当不再可能保持向后兼容性时,就会引入 ABI 更改。
由于 GCC-5 ABI 发生了重大变化,它的引入方式是,如果您将新旧二进制文件组合在一起,它会尝试故意破坏您的构建。它通过在二进制级别重命名std::string和类来实现这一点。std::list这会传播到具有std::string或std::list参数的所有函数和模板。如果您尝试在std::string针对不兼容的 ABI 版本编译的翻译单元之间传递,您的程序将无法链接。该机制并非 100% 万无一失,但它可以捕获许多常见情况。
另一种选择是默默地生成损坏的可执行文件,这是没有人想要的。
双 ABI 是新版本 GCC 标准库二进制文件与旧可执行文件保持兼容的一种方式。std::string基本上,涉及和的所有内容都有两个版本std::list,链接器具有不同的符号名称,因此使用旧版本名称的旧程序仍然可以加载和运行。
还有一个编译标志,允许较新版本的 GCC 生成与旧版 ABI 兼容的二进制文件(并且与没有兼容性标志生成的较新二进制文件不兼容)。除非绝对必要,否则不建议使用它。
看来 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 。)
| 归档时间: |
|
| 查看次数: |
403 次 |
| 最近记录: |