今天我发现了一个有趣的案例,双 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 -c file.cc
g++ -o old.o -c 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
调用相应的函数)然后)。这些都没有帮助。
显然(这是我简短调查的结果)libstdc++ 正则表达式编译器有某种存储
std::string
的内部静态数据,当两个 ABI 不兼容的代码片段尝试使用该数据时,它会产生冲突的想法std::string
对象的布局。
所以我的问题是:
该问题在 g++/libstdc++ 的多个版本中都可以重现(我尝试了从 5.4 到 7.1 的一些版本)。 libc++ 不会出现这种情况。
问题源于为什么 libstdc++ 具有双 ABI。从这两个重要的陈述来看:(1)专门引入了关于
string
(以及与本讨论无关的其他内容)如何工作的新11号标准; (2) _GLIBCXX_USE_CXX11_ABI
独立于方言工作,用于一起编译 C++03 和 C++11。
regex
模块是在第11个标准中引入的,并在内部使用字符串。因此,您使用 basic_regex
构建 c++-11(或更高版本)模板 _GLIBCXX_USE_CXX11_ABI=0
代码。这意味着您正在使用带有 pre-c++-11 字符串实现的 c++-11 regex
对象。
这应该有效吗?根据
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,但在这里它不起作用。
例如,如果您调用某些 .so 库中定义的、使用旧 ABI 编译的 _GLIBCXX_USE_CXX11_ABI=0
,那么
foo(std::string const&)
确实很有用。然后,在新的源文件中,您希望使用旧的 ABI 编译此源。但您将使用新的 ABI 保留所有其他来源。
该问题在 g++/libstdc++ 的多个版本中都可以重现(我尝试了从 5.4 到 7.1 的一些版本)。 libc++ 不会出现这种情况。
libc++
不具有这种二元性,即单一 string
实现。
我没有给出明确的答案这个异常是从哪里来的或者为什么。我可能只是猜测有一些与
regex
、string
或 locale
相关的共享全局资源在 ABI 之间没有明确区分。不同的 ABI 会以不同的方式使用它,这可能会导致任何结果,例如异常、段错误、任何意外行为。恕我直言,我更喜欢遵守我上面提到的规则,这些规则最能反映 _GLIBCXX_USE_CXX11_ABI
和双重 ABI 的意图。
是的,这是一个 libstdc++ 错误;报告为 #118408。我希望在 2018 年首次提出这个问题时就对此进行报道 - 那时修复它可能会比今天更有价值。
问题是,尽管
std::basic_regex
和 std::regex_traits
带有 ABI 标记,但正则表达式帮助器类模板之一 std::__detail::_Scanner
却没有,尽管有 std::basic_string
数据成员。因此,在两个 ABI 下编译的 _Scanner
的成员函数彼此不兼容,但具有相同的损坏名称,并且当链接器混合和匹配时,会发生爆炸。 (_Scanner
仅用作_Compiler
的数据成员,它确实从其regex_traits
模板参数获取ABI标记,但这不足以避免此问题。)
我不知道此问题有普遍适用的解决方法。