
std::__ndk1,AOSP的STL库符号使用的命名空间为 std::__1,NDK导出接口一旦依赖了STL库,AOSP链接该库时链接器会报错,提示找不到对应的std::__1::xx符号。ANDROID_STL=c++_static|c++_shared 只能在命令行设置(如cmake -DANDROID_STL=c++_static)或在CMAKE_TOOLCHAIN_FILE指定的cmake中定义, 直接在CMakeLists.txt中定义是无效的,自然也无法覆盖上面两个位置设置的值。ANDROID_STL的值了,并根据其值定义要链接的选项, 可以开启cmake生成配置的详细日志和调试选项观察到。根据OAuth2.1标准编写的鉴权模块库使用NDK编译,业务独立程序却在AOSP编写和集成编译(虽然个人觉得完全没必要在AOSP中编译),然后提示大量编译报错,原因就是导出接口包含大量的STL库符号。
摘取部分错误信息如下:
ld.ldd: error: undefined symbol:xx(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const &, ...)编译NDK版本:android-ndk-r27d
AOSP版本:android13(vendor) and android16(system)
NDK和AOSP使用的STL库(C++ Standard Library) 不同,对比如下:
特性 | AOSP (Platform) | NDK (Apps) |
|---|---|---|
主代码仓库 |
|
|
名称空间 |
|
|
库名字 |
|
|
部署 | 作为system镜像的一部分 | 通常随APK/AAB一起打包 |
除了 __ndk1 与 __1 的命名空间差异外,还有以下不同:
libc.so(Bionic)深度耦合。NDK 的 libc++ 为了保持向后兼容性(例如让你的 App 能在 Android 5.0 到 14 上运行),会包含大量针对旧版系统 Bug 的 Workaround。_LIBCPP_HAS_MUSL_OHOS 等兼容性宏,或者在异常处理(Exception Handling)和 RTTI 上有不同的默认配置。两个库不兼容。 这种不兼容是 Android 故意设计的,称为 "Two-STL" 问题。
如果你试图在一个进程中同时加载 AOSP 的 libc++.so 和 NDK 的 libc++_shared.so,并且它们之间传递了 std::string 或 std::vector,程序会因为内存布局不匹配或符号查找错误而崩溃。
在NDK目录下搜索:
grep _LIBCPP_ABI_NAMESPACE -R * 输出如下:
toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__config: inline namespace _LIBCPP_ABI_NAMESPACE {
toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__config_site:#define _LIBCPP_ABI_NAMESPACE __ndk1查看源码如下(去除注释):
__config_site
//__config_site
#ifndef _LIBCPP___CONFIG_SITE
#define _LIBCPP___CONFIG_SITE
#define _LIBCPP_ABI_VERSION 1
#define _LIBCPP_ABI_NAMESPACE __ndk1
#define _LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS
#define _LIBCPP_HAS_NO_STD_MODULES
#define _LIBCPP_HAS_NO_TIME_ZONE_DATABASE
#define _LIBCPP_PSTL_CPU_BACKEND_THREAD
#define _LIBCPP_HARDENING_MODE_DEFAULT 2
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wmacro-redefined"
#endif__config
...
// Inline namespaces are available in Clang/GCC/MSVC regardless of C++ dialect.
// clang-format off
# define _LIBCPP_BEGIN_NAMESPACE_STD namespace _LIBCPP_TYPE_VISIBILITY_DEFAULT std { \
inline namespace _LIBCPP_ABI_NAMESPACE {
# define _LIBCPP_END_NAMESPACE_STD }}
...另外NDK中虽然也有libc++.so/libc++.a库,但是其只是一个文本文件(并不是elf动态库),转链接到libc++_shared.so/libc++_static库,如下:
$cat ./toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/34/libc++.so
INPUT(-lc++_shared)
$cat ./toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/34/libc++.a
INPUT(-lc++_static -lc++abi)下载对应版本AOSP源码,并把模块源码放在external目录下,直接编写Android.bp增加cc_library_shared模块配置,然后source build/envsetup.sh选择版本后m module_name直接编译,具体配置过程略。
网上有些说直接修改NDK头文件中的定义,是可以这么操作,但是这样子会全局污染,相当于使用NDK编译的其他所有工程都会受到影响,本人采用巧妙定义,只影响当前工程,并且通过宏开关进行控制。
步骤如下:
在AOSP编译后的out目录或目标设备的/system/lib64/目录并拷贝libc++.so, 放到当前工程的prebuilts/libs/arm64-v8a目录下
通过-include编译选项注入工程中所有模块的编译
CMakeLists.txt
cmake_minimum_required(VERSION 3.19)
project(std_namespace_test VERSION 1.0.0 LANGUAGES CXX C)
option(NDK_BUILD_FOR_AOSP "Build for AOSP" OFF) #定义开关默认是关闭的
add_library(prebuilt_libc++ SHARED IMPORTED) #设置预编译库目标
set_target_properties(prebuilt_libc++ PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/prebuilts/libs/${ANDROID_ABI}/libc++.so)
set(AOSP_STL "") //默认不依赖AOSP_STL
if (NDK_BUILD_FOR_AOSP)
add_compile_definitions(-DNDK_BUILD_FOR_AOSP) #把宏传递给代码
# 创建构建目录下的生成文件目录
set(GENERATED_DIR "${CMAKE_BINARY_DIR}/generated")
file(MAKE_DIRECTORY ${GENERATED_DIR})
# 生成自定义配置头文件
set(MY_CONFIG_SITE_H "${GENERATED_DIR}/my_config_site.h")
file(WRITE ${MY_CONFIG_SITE_H}
"
#ifndef MY_CONFIG_SITE_H
#define MY_CONFIG_SITE_H
// 包含原始的__config_site
#include <__config_site>
// 覆盖ABI命名空间设置
#undef _LIBCPP_ABI_NAMESPACE
#define _LIBCPP_ABI_NAMESPACE __1
#endif // MY_CONFIG_SITE_H
")
#为所有后续目标添加-include选项,因为有宏保护,所以只会引入一份<__config_site>,并且-include保证先引入重定义名称空间后的版本
add_compile_options(-include "${MY_CONFIG_SITE_H}")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${MY_CONFIG_SITE_H}")
set(AOSP_STL prebuilt_libc++)
#link_libraries(prebuilt_libc++)
endif(NDK_BUILD_FOR_AOSP)
add_library(std_namespace_test SHARED src/std_namespace_test.cpp)
target_include_directories(std_namespace_test PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(std_namespace_test ${AOSP_STL})
target_link_options(std_namespace_test PRIVATE "-Wl,--allow-shlib-undefined") #libc++.so依赖的AOSP的基础库这边没弄,这边忽略掉
add_executable(main_std_namespace_test src/main.cpp)
set_target_properties(main_std_namespace_test PROPERTIES OUTPUT_NAME std_namespace_test)
target_include_directories(main_std_namespace_test PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(main_std_namespace_test ${AOSP_STL} std_namespace_test)
target_link_options(main_std_namespace_test PRIVATE "-Wl,--allow-shlib-undefined")libstdc++ 和libc++对比
特性 |
|
|
|---|---|---|
所属项目 | GNU项目 (GCC编译器套件的一部分) | LLVM项目 (Clang编译器套件的一部分) |
默认状态 | 是绝大多数Linux发行版的默认C++库 | 在macOS和iOS上是系统默认库,在Linux上则作为可选库存在 |
生态兼容性 | 极高,是Linux开源生态事实上的标准,几乎所有Linux下的C++库都是用其编译的 | 相对较低,混合使用可能导致链接问题 |
优势场景 | 追求最大兼容性,适用于绝大多数Linux C++开发项目 | 追求最新的C++标准特性,与Clang编译器的深度集成,以及更清晰的错误诊断信息 |
libstdc++**:Linux 生态的基石**
libstdc++是GCC(GNU Compiler Collection)的一部分,伴随Linux系统数十年,是绝大多数Linux C++项目的默认依赖。在Ubuntu系统中,你通常能找到它的动态库文件 libstdc++.so 及其对应的开发包 libstdc++-dev。
libc++**:现代化的选择**
如果你使用Clang编译器,并希望拥抱更新的C++标准或获得更现代化的标准库体验,可以选择 libc++。在Ubuntu中,可以通过 sudo apt install libc++-dev 安装。不过,如无特殊需求,使用默认的 libstdc++ 可以避免许多潜在的兼容性问题。
分类 | 库名称 (NDK 中的名称) | 提供者/所属项目 | 库文件名 | 特点 / 关键区别 | 当前状态 (截至 NDK r18+) |
|---|---|---|---|---|---|
系统库 |
| Android 系统 |
| 最小化 C++ 运行时库,功能极简,不含标准容器、算法等 STL 组件,也不支持 C++ 异常和 RTTI。 | 系统自带,功能有限,通常不用于真正的 C++ 开发。 |
已废弃库 |
| GCC (GNU) |
| GNU 标准 C++ 库(libstdc++)的 Android 移植版。支持 STL、异常和 RTTI。 | 已废弃 (NDK r18 后移除)。与 Clang 编译器存在部分冲突。 |
| STLport 项目 |
| 基于 SGI STL 的多平台实现,可移植性强。支持 STL、异常和 RTTI。 | 已废弃 (NDK r18 后移除)。自 2008 年以来长期未活跃更新。 | |
当前推荐库 |
| LLVM (libc++) |
| 官方当前默认及唯一推荐的 C++ 库。它是 LLVM 的 C++ 标准库实现,设计更现代,支持最新的 C++ 标准。支持 STL、异常和 RTTI。 | 当前标准,所有新项目都应使用此库。 |
辅助/过渡库 |
| 第三方 |
| 一个独立的 C++ ABI(应用程序二进制接口)库,主要为 | 伴随 |
参考程序如下:
std_namespace_test.h
#ifndef STD_NAMESPACE_TEST_H
#define STD_NAMESPACE_TEST_H
#include <string>
#include <vector>
#include <iostream>
#if defined(__GNUC__) || defined(__clang__)
#define DLL_PUBLIC __attribute__((visibility("default")))
#define DLL_LOCAL __attribute__((visibility("hidden")))
#else
#define DLL_PUBLIC
#define DLL_LOCAL
#endif
DLL_PUBLIC std::string Vector2String(std::vector<int> vec);
#endif // STD_NAMESPACE_TEST_Hmain.cpp
//clang++ -E -Itest/include -std=gnu++17 test/src/main.cpp 查看宏处可以,可以看到系统头文件
#include "std_namespace_test.h"
#include <map>
#define STRINGIFY(x) #x
#define DEBUG_SHOW_MACRO(x) printf(#x " = " STRINGIFY(x) "\n")
int main(int argc, char const* argv[])
{
#ifndef _LIBCPP_ABI_NAMESPACE
#define _LIBCPP_ABI_NAMESPACE "undefined" //linux系统编译,使用的libstdc++没有定义这个宏
#endif
DEBUG_SHOW_MACRO(_LIBCPP_ABI_NAMESPACE);
// 创建并初始化整数向量
std::vector<int> vec = {1, 2, 3, 4};
// 将向量转换为字符串
std::string str = Vector2String(vec);
std::cout << str << std::endl; // Output: "1, 2, 3, 4"
std::map<std::string, int> test_map{{str, 1}, {argv[0], argc}};
for (auto& [key, value] : test_map) {
std::cout << key << ": " << value << std::endl;
}
return 0;
}std_namespace_test.cpp
#include "std_namespace_test.h"
std::string Vector2String(std::vector<int> vec)
{
std::string result;
for (int i = 0; i < vec.size(); ++i) {
result += std::to_string(vec[i]);
if (i != vec.size() - 1) {
result += ", ";
}
}
return result;
}CMakePresets.json
{
"version": 4,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20
},
"configurePresets": [
{
"name": "default",
"hidden": true,
"generator": "Ninja",
"cacheVariables": {
"CMAKE_CXX_STANDARD": "17",
"CMAKE_EXPORT_COMPILE_COMMANDS": true
}
},
{
"name": "debug",
"displayName": "Debug Configuration",
"description": "Build with debug symbols and minimal optimizations.",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
},
"environment": {
"CFLAGS": "-O0 -g",
"CXXFLAGS": "-O0 -g"
}
},
{
"name": "release",
"displayName": "Release Configuration",
"description": "Build with full optimizations and strip debug symbols.",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
},
"environment": {
"CFLAGS": "-O3 -DNDEBUG",
"CXXFLAGS": "-O3 -DNDEBUG",
"LDFLAGS": "-Wl,-s"
}
},
{
"name": "android-arm64-release",
"displayName": "Android ARM64 Release",
"description": "Cross-compile for Android ARM64 (AArch64).",
"inherits": "release",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_NDK}/build/cmake/android.toolchain.cmake",
"ANDROID_ABI": "arm64-v8a",
"ANDROID_PLATFORM": "android-24",
"ANDROID_STL": "c++_static",
"CMAKE_EXE_LINKER_FLAGS": "-Wl,-s",
"CMAKE_SHARED_LINKER_FLAGS": "-Wl,-s"
}
},
{
"name": "android-x86_64-release",
"displayName": "Android x86_64 Release",
"description": "Cross-compile for Android x86_64 (x86_64).",
"inherits": "release",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_NDK}/build/cmake/android.toolchain.cmake",
"ANDROID_ABI": "x86_64",
"ANDROID_PLATFORM": "android-24",
"ANDROID_STL": "c++_static"
}
},
{
"name": "linux-x86_64-release",
"displayName": "Linux x86_64 Release",
"description": "Build for Linux x86_64.",
"inherits": "release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_C_COMPILER": "$env{ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang",
"CMAKE_CXX_COMPILER": "$env{ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++"
}
}
],
"buildPresets": [
{
"name": "build-android-arm64-release",
"configurePreset": "android-arm64-release",
"displayName": "Build Args for Android",
"nativeToolOptions": [
"-j8"
]
}
]
}CMakeUserPresets.json
{
"version": 4,
"configurePresets": [
{
"name": "base-android-config",
"hidden": true,
"environment": {
"ANDROID_NDK": "/vendor/toolchain/android-ndk-r27-linux/android-ndk-r27d"
}
},
{
"name": "user-android-arm64-release",
"inherits": [
"android-arm64-release",
"base-android-config"
],
"cacheVariables": {
"NDK_BUILD_FOR_AOSP": "ON"
}
},
{
"name": "user-android-x86_64-build",
"inherits": [
"android-x86_64-release",
"base-android-config"
]
},
{
"name": "user-linux-x86_64-release",
"inherits": [
"linux-x86_64-release",
"base-android-config"
]
}
]
}cmake --preset user-android-arm64-release -Bbuild
cmake --build build编译结果查看,成功链接到libc++.so, 并且对应导出函数的名称空间也正确链接
#readelf -d build/libstd_namespace_test.so
Dynamic section at offset 0x65e0 contains 26 entries:
标记 类型 名称/值
0x0000000000000001 (NEEDED) 共享库:[libc++.so]
0x0000000000000001 (NEEDED) 共享库:[libm.so]
0x0000000000000001 (NEEDED) 共享库:[libdl.so]
0x0000000000000001 (NEEDED) 共享库:[libc.so]
0x000000000000000e (SONAME) Library soname: [libstd_namespace_test.so]
#objdump -T build/std_namespace_test | grep Vector2String
0000000000000000 DF *UND* 0000000000000000 Base _Z13Vector2StringNSt3__16vectorIiNS_9allocatorIiEEEE
#objdump -T build/libstd_namespace_test.so | grep Vector2String
0000000000002458 g DF .text 0000000000000168 Base _Z13Vector2StringNSt3__16vectorIiNS_9allocatorIiEEEE
#c++filt _Z13Vector2StringNSt3__16vectorIiNS_9allocatorIiEEEE
Vector2String(std::__1::vector<int, std::__1::allocator<int> >) cmake --preset user-linux-x86_64-release -Bbuild
cmake --build build#ldd build/std_namespace_test
linux-vdso.so.1 (0x00007fff9ad3c000)
libstd_namespace_test.so => test/build/libstd_namespace_test.so (0x00007f61c6e9e000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f61c6c9c000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f61c6b4d000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f61c6b32000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f61c6940000)
/lib64/ld-linux-x86-64.so.2 (0x00007f61c6eab000)原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。