CPP兼容性问题分析
现象描述
若导出的 API 使用了 C++ 标准库类型(如 std::vector、std::map、std::string、std::shared_ptr 等),在不同编译环境、编译器、标准库版本 .so 调用时可能出现编译、链接或运行时异常,主要表现为:
链接符号未定义(undefined symbol)
模板实例化或类型信息不一致导致崩溃
接口数据结构错乱、内存访问异常
结构体成员内容失真,数据读取错误
ABI 与 API 的区别
API(应用编程接口):即头文件中的接口、类型和函数声明,描述了编译器和程序员可以用来编程的内容。
ABI(应用二进制接口):是编译后实体(如对象布局、名称修饰、虚表等)的具体二进制格式,决定不同二进制模块(如动态库、应用)之间能否兼容并联动。
库 API + 编译器 ABI = 库 ABI
不同编译器(或同一编译器的不同版本/不同参数)生成的 ABI 规则可能不同,同样的 API 编译出来二进制层面可能不兼容。
只有库 API 和编译 ABI 规范都一致,最终的库 ABI 才和应用程序期待的一致。二者只要有一方不一致,库 ABI 就不一样,直接影响到二进制兼容问题。
进一步说明
- C++ 的 ABI 由 多方面共同决定。 编译器实现、标准库实现、编译选项
- 不同操作系统、编译器(GCC/Clang/MSVC)、标准库版本,以及不同
-std=c++XX/-O2/-g/各种 ABI 宏参数,均会影响生成的二进制布局。
编译器实现不同:分别用 GCC 和 MSVC 这两种编译器来编译。
GCC 符号:_Z8set_nameR6PersonRKSs
MSVC 符号:?set_name@@YAXAUPerson@@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z
标准库实现不同:C++11 前后一些关键类的名字在 ABI 层面生了改变(比如 std::string 和 std::list),导致 C++11 之前和之后编译出来的目标文件不兼容。换句话说如果某个库的提供方使用的是 C++11 之前的 ABI 编译的,那么依赖这个库的项目必须也用旧的 ABI 编译。
编译选项不同:GCC 的
_GLIBCXX_USE_CXX11_ABI,不同设置会导致std::string、std::vector等内部结构变化。
STL 库实现差异导致的 ABI 不兼容
现象描述
在编译 so 时使用的是 LLVM libc++,在另一平台使用该 so 时,编译报链接错误,缺少在 std::__ndk1 下对应的实现(so 中提供的是 std::__1 下的实现)。该现象存在于使用 C++ 的 STL 库的地方。
std::__1 是 LLVM libc++(比如 macOS、iOS、部分非 NDK 构建下)的内部命名空间。

std::__ndk1 是 Android NDK 下 LLVM libc++ 实现的 STL(标准模板库)内部命名空间。

__1 与 __ndk1 的代码和接口几乎一样,但 二进制符号完全隔离,任何跨命名空间混用都是致命错误。
使用命令 nm -DC xxx.so | grep shared_ptr 查看
| 项 | std::__1 | std::__ndk1 |
| 适用平台 | macOS/iOS/Xcode/部分Linux | Android NDK |
| 头文件实现路径 | cxx-stl/llvm-libc++/include | cxx-stl/llvm-libc++/include |
| 名字空间 | std::__1 | std::__ndk1 |
| 二进制符号名 | mangled __1 | mangled __ndk1 |
| 兼容性 | 不能混用 | 不能混用 |
std::__1 vs std::__ndk1 是库 ABI 差异的一种(属于标准库实现者特意增加的符号级 ABI 隔离策略),而不是库 API 差异,也不是单纯的编译器 ABI 差异。
具体示例
std::shared_ptr
_// 实现_
void EventChannel::setStreamHandler(std::shared_ptr<StreamHandler> _handler_)
{
auto requestHandler =
std::make_shared<IncomingStreamRequestHandler>(handler, messenger_, name_);
if (taskQueue_ != nullptr) {
messenger_->setMessageHandler(name_, requestHandler,
std::shared_ptr<BinaryMessenger::TaskQueue>(taskQueue_));
}
else {
messenger_->setMessageHandler(name_, requestHandler);
}
}
_// 调用_
mTestEventHandler = std::make_shared<MyStreamHandler>();
mTestEventChannel->setStreamHandler(mTestEventHandler);
std::shared_ptr std::string
_// 实现_
EventChannel::EventChannel(std::shared_ptr<BMessenger> messenger, const std::string &channelName)
: EventChannel(messenger, channelName, nullptr) {}
_// 调用_
std::shared_ptr<DMessenger> messenger = executor->getDMessenger();
my_class::EventChannel eventChannel(messenger, "com.example.stream");
解决思路
新旧 ABI 共存(只适用于 GCC(libstdc++)系列的 STL):
通过在新实现的命名空间加std::__cxx11::前缀(inline namespace),新老实现的类型获得不同的符号。例如:
std::__cxx11::list<int>vsstd::list<int>控制 ABI 切换的宏(只适用于 GCC(libstdc++)系列的 STL):
-D_GLIBCXX_USE_CXX11_ABI=1(默认,为新 ABI),设为 0 则用老 ABI。不同源码文件可以单独选择使用新/旧 ABI。采用 C 接口:
- C 接口是明确规范的二进制接口,任何平台都能兼容,是业界跨模块/语言边界的标准方案。
- C++ 可以自由引用 C 的代码而不出现二进制兼容问题。
官方建议
编译相互依赖的代码时,不要混用新旧 ABI 产物
ABI Policy and Guidelines
最佳方案
- 对外只暴露 C 接口
- 标准库/模板/复杂对象全部只在 C++ 内部使用
- 修改代码生成新 so 后,与原先 so 进行 ABI 检查,确保兼容性
- 可参考开源项目的 C 接口暴露方法,如 RocksDB
简单修改示例
std::vector 封装
头文件(C 接口)
//vector_c_api.h
typedef void* VectorHandle;
VectorHandle vector_create();
void vector_destroy(VectorHandle v);
void vector_push_back(VectorHandle v, int value);
int vector_get(VectorHandle v, size_t index);
size_t vector_size(VectorHandle v);
C++ 实现(so 内部)
#include "vector_c_api.h"
#include <vector>
struct VectorWrapper {
std::vector<int> vec;
};
VectorHandle vector_create() {
return new VectorWrapper();
}
void vector_destroy(VectorHandle _v_) {
delete static_cast<VectorWrapper*>(v);
}
void vector_push_back(VectorHandle _v_, int _value_) {
static_cast<VectorWrapper*>(v)->vec.push_back(value);
}
int vector_get(VectorHandle _v_, size_t _index_) {
return static_cast<VectorWrapper*>(v)->vec[index];
}
size_t vector_size(VectorHandle _v_) {
return static_cast<VectorWrapper*>(v)->vec.size();
}
具体代码分析
由于在我的项目中遇到的是 STL 库实现差异导致的 ABI 不兼容,所以仅针对 STL 库进行修改。
思路是把 STL 形参改为 C 语言形式的形参,然后函数内部转换回 STL 形式。
示例 1
原始代码
void EventChannel::setStreamHandler(std::shared_ptr<StreamHandler> _handler_)
{
auto requestHandler =
std::make_shared<IncomingStreamRequestHandler>(handler, messenger_, name_);
if (taskQueue_ != nullptr) {
messenger_->setMessageHandler(name_, requestHandler,
std::shared_ptr<BinaryMessenger::TaskQueue>(taskQueue_));
}
else {
messenger_->setMessageHandler(name_, requestHandler);
}
}
修改后
void EventChannel::setStreamHandler(StreamHandler *_handler_)
{
auto handler_sp = std::shared_ptr<StreamHandler>(handler, [](StreamHandler *) {});
auto requestHandler =
std::make_shared<IncomingStreamRequestHandler>(handler_sp, messenger_, name_);
if (taskQueue_ != nullptr) {
messenger_->setMessageHandler(name_, requestHandler,
std::shared_ptr<BinaryMessenger::TaskQueue>(taskQueue_));
}
else {
messenger_->setMessageHandler(name_, requestHandler);
}
}
示例 2
原始代码
void showSystemOverlays(std::vector<const std::string> _overlays_) {
if (overlays.empty()) {
ALOGD("showSystemOverlays overlays is null");
return;
}
ALOGD("showSystemOverlays called");
}
修改后
void showSystemOverlays(const char* const* _overlays_, size_t _count_) {
if (overlays == nullptr || count == 0) {
ALOGD("showSystemOverlays overlays is null or count==0");
return;
}
std::vector<std::string> overlay_list;
overlay_list.reserve(count);
for(size_t i=0; i<count; ++i) {
_ // NULL_
if (overlays[i])
overlay_list.emplace_back(overlays[i]);
else
overlay_list.emplace_back();
}
ALOGD("PlatformMessageHandlerImpl::showSystemOverlays called, overlays count = %zu", count);
}
修改模板
std::shared_ptr
_//h_
void process_foo(void* _foo_);
_//cpp_
class Foo {};
void process_foo(void* _foo_) {
Foo* p = static_cast<Foo*>(foo);
_ // pC++_
}
std::string
_//h_
void process_string(const char* _str_);
_//cpp_
void process_string(const char* _str_) {
std::string s(str);_ // std::string_
_// s_
}
std::vector
_//h_
void process_numbers(const int* _arr_, size_t _count_);
void process_strings(const char* const* _arr_, size_t _count_);
_//cpp_
void process_numbers(const int* _arr_, size_t _count_) {
std::vector<int> v(arr, arr + count);
_ // ..._
}
void process_strings(const char* const* _arr_, size_t _count_) {
std::vector<std::string> vec;
vec.reserve(count);
for (size_t i = 0; i < count; ++i) {
if (arr[i]) vec.emplace_back(arr[i]);
else vec.emplace_back();
}
_ // ..._
}
std::map
_//h_
typedef struct {
const char* key;
int value;_ // or double/const char*_
} MapItem;
void process_map(const MapItem* _items_, size_t _count_);
_//cpp_
void process_map(const MapItem* _items_, size_t _count_) {
std::map<std::string, int> m;
for (size_t i = 0; i < count; ++i) {
m[items[i].key] = items[i].value;
}
_ // ...C++ map_
}
ABI 检测
使用工具
abigail-tools
安装
sudo apt install abigail-tools
基本命令
abidiff libfoo_1.so libfoo_2.so
返回值
参考:abidiff
| 名称 | 位值 | 说明 |
| ABIDIFF_ERROR | 1 | 有错误 |
| ABIDIFF_USAGE_ERROR | 2 | 用法错误(必须带 ABIDIFF_ERROR) |
| ABIDIFF_ABI_CHANGE | 4 | 有 ABI 变化 |
| ABIDIFF_ABI_INCOMPATIBLE_CHANGE | 8 | 有 ABI 不兼容变化(必须带 ABI_CHANGE) |
常见情况对 ABI 的影响
| 变化类型 | 是否破坏 ABI | 说明 |
| 增加/删除公有接口 | 删除/修改才破坏 | 增加一般安全 |
| 结构体成员增删/重排 | 是 | 会导致对象布局变化 |
| 虚表/多态函数增删/重排 | 是 | 运行时出错 |
| 静态/全局变量符号增删 | 是 | 链接错误 |
| 编译选项/宏改变 | 可能 | 看是否影响布局/符号 |
| STL/三方库升级 | 可能 | 与所用编译器相关 |
