std::regex和双ABI

13

今天我发现了一个有趣的案例,涉及到双重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函数中调用相应的函数)。但这些都没有帮助。

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

因此,我的问题是:

  • 这个问题是否有解决方法?
  • 这应该被视为libstdc++的一个错误吗?

这个问题在几个版本的g++/libstdc++中都可以重现 (我尝试了5.4到7.1的几个版本),但在libc++中不会出现。

1个回答

5
问题的根源在于为什么libstdc++具有双ABI。从这两个重要声明中可以看出:(1)它是为了符合新的第11标准而特别引入的,涉及到string(和其他与本讨论无关的内容)的工作方式;(2)_GLIBCXX_USE_CXX11_ABI独立于方言工作,并用于同时编译C++03和C++11。 regex模块是在第11标准中引入的,并在内部使用字符串。因此,您可以使用_GLIBCXX_USE_CXX11_ABI=0构建c++-11(或更高版本)模板basic_regex代码。这意味着您正在使用c++-11 regex对象与早期的c++-11实现字符串。
这样会起作用吗?取决于regex如何使用字符串,如果它依赖于新实现(例如,禁止写时复制),则不会,否则会。会发生什么?任何事情都可能发生。
在使用后C++03方言(即C++11、14、17等)的新代码中,您不应该使用_GLIBCXX_USE_CXX11_ABI=0,因为它引入了与标准对象的新保证不兼容的实现,特别是std::string。你可以在std>=c++-11时使用_GLIBCXX_USE_CXX11_ABI=0吗?GCC开发人员已经考虑到您可以使用旧ABI运行新功能,这有利于在旧共享库上运行新功能。然而,这可能不是一个好主意,因为代码是在新标准下编写的,但标准库却不符合这个标准,以后可能会出问题。您的问题就是这种情况的一个例子。您可以通过混合两个ABI来解决问题,但现在我们遇到了无法工作的问题。_GLIBCXX_USE_CXX11_ABI=0确实可用,例如,在某个.so库中定义了foo(std::string const&),它是使用旧的ABI编译的。然后,在您的新源文件中,您想要使用旧的ABI编译此源。但您将所有其他源保留为新的ABI。
问题在几个版本的g++/libstdc++中都可以重现(我尝试了从5.4到7.1的几个版本)。这种情况在libc++中不会发生,即单个string实现。我不能明确回答为什么或者为什么会出现此异常。我只能猜测与regex、string或locale相关的某些共享全局资源没有清晰地区分ABI有关。而不同的ABI以不同的方式处理它,可能导致任何意外行为,例如异常、段错误等。在我看来,我更喜欢遵循上述规则,这些规则最能反映_GLIBCXX_USE_CXX11_ABI和双ABI的意图。

所以,您可以使用“_GLIBCXX_USE_CXX11_ABI=0”构建c++-11(或更高版本)的template basic_regex代码。这本身并不是问题。当整个程序都使用“_GLIBCXX_USE_CXX11_ABI=0”构建时,它可以完美地工作。如果像您所说的那样,“std::regex”不能与旧的ABI一起使用,那么当包含“<regex>”时,libstdc++应该立即出现“#error”。我不知道libc++没有双ABI,谢谢! - n. m.
“这本身并不是一个问题。” @n.m. 我已经更新了答案。在我看来,这确实是一个问题,不是最终的问题,而是根源性的问题。 - Yuki
有一些与正则表达式相关的共享全局资源。这也是我的猜测。当我在调试器中运行时,我看到了一个带有巨大 size参数的new调用,实际上当作指针查看时是有意义的。因此,这与两个版本的std :: string布局重叠是一致的。 - n. m.
是的,我看到了完全相同的问题,它来自 _M_mutate,这正是与在旧 ABI 中使用写时复制相关的函数,简单地说,就是在两个独立的字符串对象之间使用共享内存。 - Yuki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接