今天我发现了一个有趣的案例,涉及到双重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++中不会出现。
size
参数的new
调用,实际上当作指针查看时是有意义的。因此,这与两个版本的std :: string布局重叠是一致的。 - n. m._M_mutate
,这正是与在旧 ABI 中使用写时复制相关的函数,简单地说,就是在两个独立的字符串对象之间使用共享内存。 - Yuki