std::regex 和双 ABI

std::regex and dual ABI

今天我发现了一个有趣的双 libstdc++ ABI 影响库兼容性的案例。

长话短说,我有两个库都在内部使用 std::regex。一个是使用 CXX11 ABI 构建的,一个不是。当这两个库在一个可执行文件中链接在一起时,它会在启动时崩溃(在输入 main 之前)。

这些库是不相关的,不会公开提及任何 std:: 类型的接口。我认为这样的库应该不受双重 ABI 问题的影响。显然不是!

问题可以通过以下方式轻松重现:

// file.cc
#include <regex>
static std::regex foo("(a|b)");

// main.cc
int main() {}

// build.sh
g++ -o new.o file.cc
g++ -o old.o file.cc -D_GLIBCXX_USE_CXX11_ABI=0 
g++ -o main main.cc new.o old.o
./main

输出为:

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

无论我做什么,问题仍然存在。 file.cc可以做成两个独立的源文件,编译成独立的共享库,两个std::regex对象可以有不同的名字,可以做成全局的,静态的或自动的(需要调用相应的函数)从 main 然后)。 None 有帮助。

显然(这是我的简短调查结果)libstdc++ 正则表达式编译器有某种内部静态数据存储 std::string,当两个 ABI 不兼容的代码片段试图使用该数据时, 它对 std::string 个对象的布局产生了矛盾的想法。

所以我的问题是:

该问题在多个版本的 g++/libstdc++ 中重现(我尝试了 5.4 到 7.1 中的几个)。它不会出现在 libc++ 中。

问题的根源在于libstdc++为什么有dual ABI。从这两个重要陈述:(1)它被专门引入以符合关于 string(以及与此讨论无关的其他内容)如何工作的新第 11 标准; (2) _GLIBCXX_USE_CXX11_ABI独立于方言工作,用于C++03和C++11一起编译。

regex 模块在第 11 版标准中引入,并在内部使用字符串。因此,您使用 _GLIBCXX_USE_CXX11_ABI=0 构建您的 c++-11(或更高版本)模板 basic_regex 代码。这意味着您正在使用 c++-11 regex 对象和字符串的 pre-c++-11 实现。

应该行吗?根据 regex 如何使用字符串,如果它确实依赖于新的实现(例如禁止写时复制),则否,否则是。会发生什么?随便。

归根结底,您不应该在任何使用 post-c++-03 方言(即 c++-11,14,17,...)的新代码上使用 _GLIBCXX_USE_CXX11_ABI=0 ,因为它引入了与标准对象的新保证不兼容的实现,特别是 std::string.

我可以将 _GLIBCXX_USE_CXX11_ABI=0 与 std>=c++-11 一起使用吗? GCC 开发人员注意您可以 运行 使用旧 ABI 的新东西,它受益于具有新功能的可能性 运行 旧共享库。然而,这可能不是一个好主意,也因为代码是在一个新标准中,但是标准库不符合这个标准,以后可能会变得很糟糕。你的问题就是一个例子。您可以混合使用两个 ABI,但我们现在无法正常工作。

_GLIBCXX_USE_CXX11_ABI=0 如果您调用在某些 .so 库中定义的 foo(std::string const&) 是真正有用的,使用旧的 ABI 编译。然后在你的新源文件中你想用旧的 ABI 编译这个源。但所有其他来源都将保留在新的 ABI 中。

The problem is reproducible in several versions of g++/libstdc++ (I tried a few from 5.4 to 7.1). It doesn't occur with libc++.

libc++ 没有这种双重性,即单一 string 实现。

我没有给出此异常的来源或原因的明确答案。我可能只是猜测有一些与 regexstringlocale 相关的共享全局资源在 ABI 之间没有明确区分。不同的 ABI 以不同的方式处理它,这可能会导致任何结果,例如异常、段错误、任何意外行为。恕我直言,我更愿意遵守我上面提到的规则,这些规则最能反映 _GLIBCXX_USE_CXX11_ABI 和双重 ABI 的意图。