首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >std::ndk1与std::1不匹配编译错误引发的Android_NDK接口设计思考

std::ndk1与std::1不匹配编译错误引发的Android_NDK接口设计思考

原创
作者头像
Uncle匠
修改2026-04-14 09:52:18
修改2026-04-14 09:52:18
430
举报

结论

  • Android编写native层本地程序时,能使用NDK编译独立二进制程序时尽量直接使用NDK编译,即使用NDK编译二进制及其依赖的库,而不要使用AOSP编写和编译独立的二进制程序。NDK编译的二进制可以直接部署到Android系统的native层,如system或vendor等目录。
    • NDK编译快,依赖少,只需要下载NDK包,不需要下载AOSP源码,不需要像AOSP那样需要每次编译预处理时间特别长, 因为需要扫描所有目录构建所有make编译依赖树,虽然AOSP可以直接mmm module_dir或make module_name直接编译目标模块,但每次编译扫描构建依赖会浪费大量的时间,不适合快速修改迭代。
    • 如果依赖了AOSP的库接口而NDK里又没有对应的功能接口则必须使用AOSP编译,如需在native层调用C++服务获取接口从而调用系统服务,目前NDK的库没有提供获取服务的接口。当然对于此种情况,也可以设计的轻量的库依赖AOSP系统,随AOSP系统一并编译发布,其他大部分程序功能仍然使用NDK编译,需要使用AOSP库时通过dlopen动态加载来调用。
  • 如果NDK编译的二进制程序或动态库使用了STL库,则静态链接libc++_static库,对于动态库如果有导出接口给别人使用,导出接口不要依赖任何STL库类型,导出接口使用纯C接口形式,这样的库可以被NDK和AOSP程序同时依赖使用。如果导出接口不方便去除STL库,则编写Android.bp专门编译一个AOSP版本库给AOSP程序使用。
    • NDK的STL库和AOSP的STL的名称空间不同, NDK的STL库符号使用的命名空间为 std::__ndk1,AOSP的STL库符号使用的命名空间为 std::__1,NDK导出接口一旦依赖了STL库,AOSP链接该库时链接器会报错,提示找不到对应的std::__1::xx符号。
    • 如果确实想NDK编译导出包含STL的库被AOSP正确链接,本文也提供了临时简易的方法来解决编译问题,运行时建议进行充分测试避免兼容性问题,否则不建议生产环境使用。
  • 命令行-include xx.h可以在编译任意cpp单元时都事先引入xx.h,但是include的优先级低于-D宏定义,与传递顺序无关 - "CMAKE_CXX_FLAGS": "-include config_site -U_LIBCPP_ABI_NAMESPACE -D_LIBCPP_ABI_NAMESPACE=1" 这个会在include前先定义-U_LIBCPP_ABI_NAMESPACE -D_LIBCPP_ABI_NAMESPACE=1, 即_LIBCPP_ABI_NAMESPACE宏可作用于config_site, 等价于: #undef _LIBCPP_ABI_NAMESPACE #define _LIBCPP_ABI_NAMESPACE 1 #include <config_site>
  • ANDROID_STL=c++_static|c++_shared 只能在命令行设置(如cmake -DANDROID_STL=c++_static)或在CMAKE_TOOLCHAIN_FILE指定的cmake中定义, 直接在CMakeLists.txt中定义是无效的,自然也无法覆盖上面两个位置设置的值。
    • 原因就是cmake是在早期阶段就使用ANDROID_STL的值了,并根据其值定义要链接的选项, 可以开启cmake生成配置的详细日志和调试选项观察到。

背景问题

根据OAuth2.1标准编写的鉴权模块库使用NDK编译,业务独立程序却在AOSP编写和集成编译(虽然个人觉得完全没必要在AOSP中编译),然后提示大量编译报错,原因就是导出接口包含大量的STL库符号。

摘取部分错误信息如下:

代码语言:txt
复制
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库差异简要汇总

NDK和AOSP使用的STL库(C++ Standard Library) 不同,对比如下:

特性

AOSP (Platform)

NDK (Apps)

主代码仓库

external/libcxx

toolchain/llvm-project

名称空间

std::__1

std::__ndk1

库名字

libc++.so

libc++_shared.so or libc++_static.a

部署

作为system镜像的一部分

通常随APK/AAB一起打包

除了 __ndk1__1 的命名空间差异外,还有以下不同:

  • ABI 稳定性策略
    • AOSP 版:是系统的一部分,其 ABI 必须在整个系统运行期间保持绝对稳定。
    • NDK 版:设计目标是“随应用发布”。NDK 版本更新更快,可能会包含一些尚未进入 AOSP 稳定版的新特性或编译器优化。
  • Bionic 耦合度:AOSP 的 libc++ 是针对特定的 Android 系统版本编译的,它与同版本的 libc.so(Bionic)深度耦合。NDK 的 libc++ 为了保持向后兼容性(例如让你的 App 能在 Android 5.0 到 14 上运行),会包含大量针对旧版系统 Bug 的 Workaround。
  • 宏定义与特性开关
    • NDK 版通常开启了 _LIBCPP_HAS_MUSL_OHOS 等兼容性宏,或者在异常处理(Exception Handling)和 RTTI 上有不同的默认配置。

两个库不兼容。 这种不兼容是 Android 故意设计的,称为 "Two-STL" 问题

如果你试图在一个进程中同时加载 AOSP 的 libc++.so 和 NDK 的 libc++_shared.so,并且它们之间传递了 std::stringstd::vector,程序会因为内存布局不匹配或符号查找错误而崩溃。

头文件源码查看NDK的STL符号名称空间

在NDK目录下搜索:

grep _LIBCPP_ABI_NAMESPACE -R * 输出如下:

代码语言:txt
复制
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

代码语言:c
复制
//__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

代码语言:c
复制
...
// 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库,如下:

代码语言:bash
复制
$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版本(推荐)

下载对应版本AOSP源码,并把模块源码放在external目录下,直接编写Android.bp增加cc_library_shared模块配置,然后source build/envsetup.sh选择版本后m module_name直接编译,具体配置过程略。

修改_LIBCPP_ABI_NAMESPACE定义(临时办法)

网上有些说直接修改NDK头文件中的定义,是可以这么操作,但是这样子会全局污染,相当于使用NDK编译的其他所有工程都会受到影响,本人采用巧妙定义,只影响当前工程,并且通过宏开关进行控制。

步骤如下:

拷贝libc++.so

在AOSP编译后的out目录或目标设备的/system/lib64/目录并拷贝libc++.so, 放到当前工程的prebuilts/libs/arm64-v8a目录下

修改cmake重定义名称空间

通过-include编译选项注入工程中所有模块的编译

CMakeLists.txt

代码语言:cmake
复制
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")

扩展和DEMO示例

各STL库区别和关系

libstdc++libc++对比

特性

libstdc++ (GNU实现)

libc++ (LLVM实现)

所属项目

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++ 可以避免许多潜在的兼容性问题。

STL 库关系与区别一览表

分类

库名称 (NDK 中的名称)

提供者/所属项目

库文件名

特点 / 关键区别

当前状态 (截至 NDK r18+)

系统库

libstdc++ (默认)

Android 系统

/system/lib/libstdc++.so

最小化 C++ 运行时库,功能极简,不含标准容器、算法等 STL 组件,也不支持 C++ 异常和 RTTI。

系统自带,功能有限,通常不用于真正的 C++ 开发。

已废弃库

gnustl_static / gnustl_shared

GCC (GNU)

libgnustl_static.a libgnustl_shared.so

GNU 标准 C++ 库(libstdc++)的 Android 移植版。支持 STL、异常和 RTTI。

已废弃 (NDK r18 后移除)。与 Clang 编译器存在部分冲突。

stlport_static / stlport_shared

STLport 项目

libstlport_static.a libstlport_shared.so

基于 SGI STL 的多平台实现,可移植性强。支持 STL、异常和 RTTI。

已废弃 (NDK r18 后移除)。自 2008 年以来长期未活跃更新。

当前推荐库

c++_static / c++_shared

LLVM (libc++)

libc++_static.a libc++_shared.so

官方当前默认及唯一推荐的 C++ 库。它是 LLVM 的 C++ 标准库实现,设计更现代,支持最新的 C++ 标准。支持 STL、异常和 RTTI。

当前标准,所有新项目都应使用此库。

辅助/过渡库

gabi++_static / gabi++_shared

第三方

libgabi++_static.a libgabi++_shared.so

一个独立的 C++ ABI(应用程序二进制接口)库,主要为 stlport 等库提供异常和 RTTI 支持。

伴随 stlport 的废弃而不再常用。

DEMO示例

代码

参考程序如下:

std_namespace_test.h

代码语言:c
复制
#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_H

main.cpp

代码语言:c
复制
//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

代码语言:c
复制
#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

代码语言: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

代码语言: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"
            ]
        }
    ]
}

编译运行

Android ARM64 (推荐)
代码语言:bash
复制
cmake --preset user-android-arm64-release -Bbuild
cmake --build build

编译结果查看,成功链接到libc++.so, 并且对应导出函数的名称空间也正确链接

代码语言:bash
复制
#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> >) 
Linux x86_64
代码语言:bash
复制
cmake --preset user-linux-x86_64-release -Bbuild
cmake --build build
代码语言:bash
复制
#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)

参考

  1. GNU与c++STL的区别与联系
  2. Android NDK 编译opencv后部分接口std::NDK1与项目std::1不匹配
  3. 在更新NDK修订版11之后出现错误,未定义对std::__NDK1的引用
  4. Android NDK string处理
  5. 不要在NDK中使用系统预编译好的C++库
  6. Android NDK issue: Converting strings to std::NDK1 instead of std::1

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结论
  • 背景问题
  • 问题分析
    • NDK和AOSP使用的STL库差异简要汇总
    • 头文件源码查看NDK的STL符号名称空间
  • 解决方法
    • 额外编译AOSP版本(推荐)
    • 修改_LIBCPP_ABI_NAMESPACE定义(临时办法)
      • 拷贝libc++.so
      • 修改cmake重定义名称空间
  • 扩展和DEMO示例
    • 各STL库区别和关系
      • STL 库关系与区别一览表
    • DEMO示例
      • 代码
      • 编译运行
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档