C++ 库支持

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

C++ 运行时库

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

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

libc++ 可作为静态库和共享库使用。

libc++

LLVM 的 libc++ 是自 Lollipop 以来 Android 操作系统一直使用的 C++ 标准库,从 NDK r18 开始,它是 NDK 中唯一可用的 STL。

CMake 默认使用 clang 的 C++ 版本(目前为 C++14),因此您需要在 CMakeLists.txt 文件中将标准 CMAKE_CXX_STANDARD 设置为适当的值才能使用 C++17 或更高版本的功能。有关更多详细信息,请参阅 CMakeCMAKE_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/lib/libstdc++.so。此库不应与 GNU 的功能齐全的 libstdc++ 混淆。在 Android 上,libstdc++ 仅为 newdelete。使用 libc++ 获取功能齐全的 C++ 标准库。

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

除了 C 库头文件的 C++ 包装器(例如 <cstdio>)之外,没有标准库支持。如果您需要 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++ 与操作系统的一部分不同。这使 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