今天,我发现了一个有趣的案例:双重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 ++ regex编译器具有某种内部静态数据,该数据存储std::string
,并且当两个不兼容ABI的代码段尝试使用该数据时,对std::string
对象的布局有相互矛盾的想法。
所以我的问题是:
该问题在g ++ / libstdc ++的多个版本中都可以重现(我尝试从5.4到7.1中的几个)。 libc ++不会发生这种情况。
答案 0 :(得分:4)
问题源于libstdc ++具有dual ABI的原因。从这两个重要的陈述中可以得出:(1)在string
(以及与该讨论无关的其他内容)的工作方式方面,为了符合新的第11标准而专门引入该标准; (2)_GLIBCXX_USE_CXX11_ABI
独立于方言而工作,用于一起编译C ++ 03和C ++ 11。
regex
模块在第11个标准中引入,并在内部使用字符串。因此,您可以使用basic_regex
构建c ++-11(或更高版本)模板_GLIBCXX_USE_CXX11_ABI=0
代码。这意味着您正在使用c ++-11 regex
对象和c ++-11之前的字符串实现。
那行得通吗?根据{{1}}使用字符串的方式,如果它确实依赖于新的实现(例如,禁止写时复制),则为否,否则为是。会发生什么?一切。
最简单的说,您不应该在使用后c ++-03方言的任何新代码(即c ++-11,14,17,...)上使用regex
,因为它引入了实现与标准对象(尤其是_GLIBCXX_USE_CXX11_ABI=0
)上的新保证不兼容。
我可以将std::string
与std> = c ++-11一起使用吗? GCC开发人员要小心,您可以使用旧的ABI运行新的东西,这可能会使新功能与旧的共享库一起运行而受益。但是,这可能不是一个好主意,因为代码是在新标准中,但是标准库不符合该标准,以后可能会出现问题。您的问题就是一个例子。您可以将两个ABI混合使用,到这里我们无法正常工作。
_GLIBCXX_USE_CXX11_ABI=0
确实是有用的,例如,调用在某个.so库中定义的_GLIBCXX_USE_CXX11_ABI=0
,该库是用旧的ABI编译的。然后,在新的源文件中,您想使用旧的ABI编译该源。但是您将使用新的ABI保留所有其他来源。
该问题在g ++ / libstdc ++的多个版本中都可以重现(我尝试从5.4到7.1中的几个)。 libc ++不会发生这种情况。
foo(std::string const&)
不具有这种双重性,即单个libc++
实现。
我没有给出明确的答案,此异常来自何处或为什么。我只能猜测,有一些与string
,regex
或string
相关的共享全局资源在ABI之间没有明确区分。并且不同的ABI对其进行不同的处理会导致什么结果,例如异常,段故障,任何意外行为。恕我直言,我更喜欢遵循上面提到的规则,这些规则最能反映出locale
和双重ABI的意图。