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 或更高版本的功能。有关更多详细信息,请参阅 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/lib/libstdc++.so。此库不应与GNU的完整功能的libstdc++混淆。在Android上,libstdc++仅仅是 newdelete。如需完整功能的C++标准库,请使用libc++。

系统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 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构建的库。

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