C++ 库支持

NDK 支持多种 C++ 运行时库。 本文件介绍这些库的相关信息、所涉及的折衷做法以及库的使用方法。

C++运行时库

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

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

libc++ 同时提供静态库和共享库。

libc++

LLVM 的 libc++ 是 C++ 标准库,且自 Lollipop 及 NDK r18 成为 NDK 唯一可用的 STL 以来,Android 操作系统便一直使用该库。

libc++ 的共享库为 libc++_shared.so,静态库为 libc++_static.a

libc++ 依据伊利诺伊大学“BSD 式”许可证 和 MIT 许可证双重授权。 如需了解详细信息,请参阅认证文件

system

system 运行时指的是 /system/lib/libstdc++.so。 请勿将该库与 GNU 的全功能 libstdc++ 混淆。 在 Android 系统中,libstdc++ 只是 newdelete。 请针对全功能 C++ 标准库使用 libc++。

系统 C++ 运行时支持基础 C++ 运行时 ABI。 基本上,此库提供 newdelete。 不同于 NDK 中提供的其他选项,此库不支持异常处理和 RTTI。

<cstdio> 等用于 C 库标头的 C++ 包装器之外,并无标准库支持。 如需 STL,您应使用本页面提供的其他选项。

none

另外,您还可选择不使用 STL。 在这种情况下,没有链接或授权要求。 不提供 C++ 标准标头。

选择 C++ 运行时

如果您要使用 CMake,则可使用您模块级 build.gradle 文件中的 ANDROID_STL 变量,指定表 1 中的一个运行时。 如需了解详情,请参阅使用 CMake 变量

如您使用的是 ndk-build,则可使用您 Application.mk 文件中的 APP_STL 变量,指定表 1 中的一个运行时。 例如:

APP_STL := c++_shared

您只能为应用选择一个运行时,并且只能在 Application.mk 中进行选择。

如果使用的是独立工具链,工具链会默认使用共享 STL。 要使用静态变体,请添加 -static-libstdc++ 至链接程序标记。

重要注意事项

静态运行时

如果应用的全部原生代码均包含于一个共享库中,我们建议使用静态运行时。 如此可让链接程序最大限度内联和精简未使用的代码,以使应用达到最优化状态且体积最小巧。 这样做还能避免旧版 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,则必须将其纳入 APK 中。 如果使用 Gradle 构建应用,则此步骤自动完成。

旧版 Android 的 PackageManager 和动态链接程序存在错误,可能导致原生库的安装、升级和加载不可靠。 特别是,如果您的应用针对早于 Android 4.3(Android API 级别 18)的 Android 版本,并且您使用 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。

您有可能依赖两个互不兼容的库。 在这种情况下,您只能弃用其中一个依赖项,或请求维护者提供一个根据其他 STL 构建的库。

C++ 异常

C++ 异常受 libc++ 支持,但其在 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

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

要在 ndk-build 中针对整个应用启用 RTTI,请添加下面这一行代码至您的 Application.mk 文件:

APP_CPPFLAGS := -frtti

要针对单一 ndk-build 模块启用 RTTI,请添加下面这一行代码至其 Android.mk 中指定的模块:

LOCAL_CPP_FEATURES := rtti

或者,您可以使用:

LOCAL_CPPFLAGS := -frtti