C++ 库支持

NDK 支持多种 C++ 运行时库。本文档提供了有关这些库、权衡取舍以及如何使用它们的信息。

C++ 运行时库

表 1. NDK C++ 运行时和功能。

名称 功能
libc++ 现代 C++ 支持。
system newdelete。(在 r18 中已弃用。)
none 无头文件,C++ 支持有限。

libc++ 既可作为静态库,也可作为共享库使用。

libc++

LLVM 的 libc++ 是 Android OS 自 Lollipop 起使用的 C++ 标准库,截至 NDK r18,它是 NDK 中唯一可用的 STL。

CMake 默认使用 clang 默认的 C++ 版本(目前是 C++14),因此您需要在 CMakeLists.txt 文件中将标准 CMAKE_CXX_STANDARD 设置为适当的值,以使用 C++17 或更高版本的功能。有关更多详细信息,请参阅 CMake 关于 CMAKE_CXX_STANDARD 的文档

ndk-build 默认也将决定权留给 clang,因此 ndk-build 用户应使用 APP_CPPFLAGS 添加 -std=c++17 或他们想要的任何其他选项。

libc++ 的共享库是 libc++_shared.so,静态库是 libc++_static.a。在典型情况下,构建系统会根据需要为用户处理这些库的使用和打包。对于非典型情况或实现您自己的构建系统时,请参阅构建系统维护者指南使用其他构建系统的指南。

LLVM 项目遵循 Apache 许可证 v2.0 并包含 LLVM 例外条款。有关更多信息,请参阅许可证文件

system

system 运行时指的是 /system/lib/libstdc++.so。这个库不应与 GNU 功能齐全的 libstdc++ 混淆。在 Android 上,libstdc++ 只是 newdelete。要使用功能齐全的 C++ 标准库,请使用 libc++。

system C++ 运行时提供对基本 C++ 运行时 ABI 的支持。本质上,这个库提供 newdelete。与 NDK 中可用的其他选项相比,它不支持异常处理或 RTTI。

除了 C 库头文件(如 <cstdio>)的 C++ 封装之外,没有标准的库支持。如果您想要 STL,应使用本页介绍的其他选项之一。

none

还有一种选择是不使用 STL。在这种情况下,没有链接或许可要求。没有可用的 C++ 标准头文件。

选择 C++ 运行时

CMake

CMake 的默认设置是 c++_static

您可以在模块级的 build.gradle 文件中使用 ANDROID_STL 变量指定 c++_sharedc++_staticnonesystem。要了解更多信息,请参阅 CMake 中关于 ANDROID_STL 的文档

ndk-build

ndk-build 的默认设置是 none

您可以在 Application.mk 文件中使用 APP_STL 变量指定 c++_sharedc++_staticnonesystem。例如

APP_STL := c++_shared

ndk-build 只允许您为应用选择一个运行时,并且只能在 Application.mk 中进行此操作。

直接使用 clang

如果您在自己的构建系统中直接使用 clang,clang++ 默认会使用 c++_shared。要使用静态变体,请将 -static-libstdc++ 添加到链接器标志中。请注意,尽管该选项出于历史原因使用了“libstdc++”名称,但它对于 libc++ 也是正确的。

重要注意事项

静态运行时

如果您应用的所有原生代码都包含在单个共享库中,我们建议使用静态运行时。这使得链接器能够尽可能多地内联和剪除未使用的代码,从而实现最优化和最小的应用。它还可以避免旧版本 Android 中 PackageManager 和动态链接器的错误,这些错误使得处理多个共享库变得困难且容易出错。

话虽如此,在 C++ 中,在单个程序中定义同一函数或对象的多个副本是不安全的。这是 C++ 标准中存在的单一定义规则的一个方面。

使用静态运行时(以及通常的静态库)时,很容易意外违反此规则。例如,以下应用违反了此规则

# Application.mk
APP_STL := c++_static
# Android.mk

include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo.cpp
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.cpp
LOCAL_SHARED_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)

在这种情况下,STL,包括和全局数据以及静态构造函数,将同时存在于两个库中。此应用的运行时行为是未定义的,实际上崩溃非常常见。其他可能的问题包括

  • 在一个库中分配的内存,在另一个库中释放,导致内存泄漏或堆损坏。
  • libfoo.so 中引发的异常在 libbar.so 中未被捕获,导致您的应用崩溃。
  • std::cout 的缓冲无法正常工作。

除了涉及的行为问题外,将静态运行时链接到多个库中会在每个共享库中复制代码,增加应用的大小。

通常,只有当您的应用中只有一个共享库时,才能使用静态版本的 C++ 运行时。

共享运行时

如果您的应用包含多个共享库,则应使用 libc++_shared.so

在 Android 上,NDK 使用的 libc++ 与 OS 中的 libc++ 不同。这使得 NDK 用户即使在面向旧版本 Android 时也能使用最新的 libc++ 功能和错误修复。权衡之处在于,如果您使用 libc++_shared.so,则必须将其包含在您的应用中。如果您使用 Gradle 构建应用,则会自动处理。

旧版本 Android 中的 PackageManager 和动态链接器存在错误,导致原生库的安装、更新和加载不可靠。特别是,如果您的应用面向的 Android 版本低于 Android 4.3(Android API 级别 18),并且您使用 libc++_shared.so,则必须先加载该共享库,然后再加载任何依赖于它的其他库。

ReLinker 项目提供了所有已知原生库加载问题的解决方案,通常是比自己编写解决方案更好的选择。

每个应用一个 STL

历史上,NDK 除了 libc++ 之外还支持 GNU libstdc++ 和 STLport。如果您的应用依赖于使用与构建您应用所用 NDK 不同的 NDK 构建的预构建库,则需要确保其兼容地执行此操作。

一个应用不应使用多个 C++ 运行时。各种 STL 相互兼容。例如,libc++ 中 std::string 的布局与 gnustl 中的不同。针对一个 STL 编写的代码将无法使用针对另一个 STL 编写的对象。这只是一个例子;不兼容之处很多。

此规则超出您的代码范围。您的所有依赖项必须使用您选择的同一 STL。如果您依赖使用 STL 且未提供每个 STL 库的闭源第三方依赖项,则您在 STL 选择上没有选择权。您必须使用与您的依赖项相同的 STL。

您可能依赖于两个相互不兼容的库。在这种情况下,唯一的解决方案是放弃其中一个依赖项或要求维护者提供针对另一个 STL 构建的库。

C++ 异常

libc++ 支持 C++ 异常,但在 ndk-build 中默认禁用。这是因为历史上 NDK 不支持 C++ 异常。CMake 和独立工具链默认启用 C++ 异常。

要在 ndk-build 中为您的整个应用启用异常,请将以下行添加到您的 Application.mk 文件中

APP_CPPFLAGS := -fexceptions

要为单个 ndk-build 模块启用异常,请在该模块的 Android.mk 文件中添加以下行

LOCAL_CPP_FEATURES := exceptions

或者,您可以使用

LOCAL_CPPFLAGS := -fexceptions

RTTI

与异常一样,libc++ 支持 RTTI,但在 ndk-build 中默认禁用。CMake 和独立工具链默认启用 RTTI。

要在 ndk-build 中为您的整个应用启用 RTTI,请将以下行添加到您的 Application.mk 文件中

APP_CPPFLAGS := -frtti

要为单个 ndk-build 模块启用 RTTI,请在该模块的 Android.mk 文件中添加以下行

LOCAL_CPP_FEATURES := rtti

或者,您可以使用

LOCAL_CPPFLAGS := -frtti