ABI, dll-boundary, STL, and unique_ptr?

@vrqq  May 1, 2020

To see a lot of reference, there's some differences of ideas about that:
Is it safety to use std::shared_ptr and std::unique_ptr at DLL boundary?

Pros:
Safe to use std::unique_ptr : https://www.codeproject.com/Articles/594671/How-to-ensure-proper-dynamic-library-boundary-cros
Another solution : https://isocpp.org/files/papers/n4028.pdf
Something about GCC: https://stackoverflow.com/questions/23874393/does-using-std-c11-break-binary-compatibility

Neutral / Cons:
Words with no reference? https://stackoverflow.com/questions/32088478/correctly-defining-dll-interfaces-with-c11-14
Suggestion about C++20 module proposal : https://softwareengineering.stackexchange.com/questions/176681/did-c11-address-concerns-passing-std-lib-objects-between-dynamic-shared-librar

The problem is

Since we package our code with dll/so, user may not use the same build environment and C-runtime-library, that we had to use the COMMON PART OF C/C++ with users.
For example, void*, char*, int, double, ... with same platform, all other c-runtime-library or header will provide the same ASM code.
The first link show a picture to explain a case.

Why not STL?
A lot of STL were provided by template, so a part of STL code will build with user's code.
So the problem is affected by compiler and STL_template, we cannot know anything in different platforms.

The result

All binaries By GCC, it's safety to use STL in dll-boundary.
While the libstdc++ with GCC4.9 lack support of std::__cxx11, it will cause link error when linking a high version of .so library.
REFs: https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html

By using Clang++ to link a library built by MSVC, it's unsafe to deliver STL in dll boundary, because of not fully compatibly.
http://clang.llvm.org/docs/MSVCCompatibility.html
(: It's under construction...

By VS 2015 or later (MSVC v140 v141 v142), it's no problem on ABI compatibility with msvcSTL behavior.
https://docs.microsoft.com/zh-cn/cpp/porting/binary-compat-2015-2017?view=vs-2019

A method to prevent: Use my own smart_ptr in header file to keep same binary-code behavior.

How can we use C-style / C++03 Construction and destruction?
You may like private-implement idea to organize the code.

class MyInterface {}
class MyClass : MyInterface {}
MyInterface *createMyClass();
void *destoryMyClass(MyInterface*);

以及为什么会有ABI

假设我们写了一个.h文件,内有结构体struct A

1 Arugment: struct A编译出来的 汇编代码 受编译器影响
参见我之前的一个疑问:https://devblogs.microsoft.com/cppblog/optimizing-the-layout-of-empty-base-classes-in-vs2015-update-2-3/
同样的代码,在不同编译器下产生 不兼容的汇编代码

2 Example 不同版本STL库代码不兼容
我们有一个.so 返回了一个std::string,它在stack上开了20 bytes空间
正好我们用了个奇特编译器my,提供的头文件里string占 32 bytes空间
.so 返回给我们的代码一个stack上面的地址addr表示string,我们用完了释放它,运行的是my::string::free(addr)
然后我们退了 32 bytes 的栈,但是我们想要退20 bytes

按理说STL库不兼容,应该是源码上面写的问题,不是ABI的范畴吧?
对!这个其实应该叫STL-API不兼容!STL库是C++标准的一部分,在使用者视角就把他归类到编译器那堆儿了
正如上文所说library API + compiler ABI = library ABI
换句话说是 STL库的二进制文件不兼容(不能相互调用)

Example续
接上个例子,如果返回的是指针指向开在heap上的空间,那么会有“指针开在不同的heap管理器上”的问题,见https://blog.vrqq.org/archives/516/ 的MD MT部分,MT就是两个heap管理器。

3 Example 编译器ABI
C++虚类类有vtable虚表,那虚表是摆在内存里最前面还是摆在最后面呢?
不同的ABI规范着不同的编译器行为,比如我们可以自己约定一个ABI要求虚表在内存里摆在class结构体最后。
那么代码都一样,都遵循c++语法,但是ABI不一样,运行起来在内存里这个变量的布局就不一样,互相调用就摸不着门路!

疑问:C++ Name mangling能解决多少问题?
我觉得很好,起码有namespace解决了std::__cxx11这个后来有的namespace和之前的冲突,以及潜在隔离了不同编译器互相瞎调用(可能会使用不同ABI标准以及编译器自家STL库)

那我又有问题了,操作系统给提供了那么多库,我啥编译器写的程序不都能调用吗?
问得好,说明操作系统给提供的库兼容性非常棒!
而且比如我是python用户,想通过某些“中间工具”调用操作系统库,那么这个“中间工具”其实是现成的,并且是已经保证了ABI兼容性的。

解决方案2
用不用STL都可以,clang和gcc之间想相互调用,想象成其他语言(dolphi)和gcc相互调用,只需要他们使用的 返回值等等接口 在汇编代码上一致就可以。
但目前来看,clang向gcc兼容还under construction,有的可以调,有的不能掉。。
因为我们编译好的库(.so)是binary 二进制的,汇编代码。
所以我们其他语言调用,“STL”兼容,指的是编译出代码和binary汇编代码兼容

ABI 编译器toolset 和向前兼容

引自:https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
以gcc举例,尽量深入,不做其他编译器对比。。

  • C++ Standard 现在有那么多比如 c++11 c++14 c++17,但是c++标准只规定了“语法”,以及STL库有什么,例如规定了std::vector要包含begin()函数,begin()返回iterator,但是对于怎么实现没有要求
  • STL库有很多家都有,比如gnu出的libstdc++,uClibc出的uclibc++,STL和c++ standard对应关系呢,可以是一个库兼容11 14 17,也可以为不同的c++ standard提供不同的分支,但是这都是做STL库的人的implementation,而C++标准并没有规定如何“实现”,只规定了语法,以及接口函数名。
  • 也就是说,某公司出了个STL库,到底做成header-only还是咋,是他自愿,只要接口符合C++ standard就ok。扩展阅读 https://zhuanlan.zhihu.com/p/23016264
  • GCC 是编译器,大名鼎鼎,老版本的gcc不能支持c++11语法,所以gcc的版本更新,对应着更多的语法支持,以及旧语法的更替。所以gcc有编译参数 -std=c++17
  • 上面说到libstdc++.so 其实是和STL里面的.cpp编译而成的,是STL的具体实现。它和编译器gcc合起来叫Toolset
    举例:

    #include <condition_variable> //这个库的接口是C++11标准规定的,这个头文件是GNU STL提供的
    condituon_variable cv;
    cv.notify_one(); //这个notify_one()对应的代码就在libstdc++.so里面

    使用这个命令一看究竟strings /lib64/libstdc++.so.6 |grep condition_variable

接下来说ABI
对于开发者来说似乎没什么关系,我们来看下ABI规定了什么
https://itanium-cxx-abi.github.io/cxx-abi/abi.html
比如规定了name mangling的规则,规定了struct的内存布局,struct的大小,对齐方式,等等。主要针对的是,其他文件想link编译好的so文件时候需要按照什么规则。我们的程序 就按照上述规则把代码变成“兼容的”汇编代码。

所以ABI只是一个针对编译器的东西,但是为什么我们的库也要说ABI兼容呢?
因为ABI规定了例如命名、vtable问题,gcc编译器针对某ABI规则 把源代码转换成binary,那么我们就说 这个binary是遵守某ABI规则的,其他程序想link,也要遵守同样的ABI规则。

为什么微软的MSVC编译的.dll和llvm系列的有所谓的 ABI不兼容呢?
其实指的是有一部分不兼容,比如msvc2015之后约定了name mangling方法是加$符号,但是itanium-ABI约定是_Z。
那我们根本不能按照.h里面的函数声明,找到这个函数的implement。

再说libstdc++和GCC的版本关系
libstdc++也是从cpp源码编译的,也不是凭空从石头缝蹦出来。
编译就要用到编译器,所以就有了ABI兼容问题。那为什么ABI会更新呢?因为需求会变。。。
然而GNU家的STL实现,最近做的向前兼容就很好。比如用很多#define保证了向前兼容,一个libstdc++.so.6里面有很多小define打包了很多之前版本


添加新评论