NDK 支持多个 C++ 运行时库。本文档提供有关这些库、相关权衡以及如何使用它们的信息。
C++ 运行时库
表 1. NDK C++ 运行时和功能。
名称 | 功能 |
---|---|
libc++ | 现代 C++ 支持。 |
system | new 和 delete 。(在 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++ 仅为 new
和 delete
。使用 libc++ 获取功能齐全的 C++ 标准库。
系统 C++ 运行时提供对基本 C++ 运行时 ABI 的支持。本质上,此库提供 new
和 delete
。与 NDK 中提供的其他选项相比,它不支持异常处理或 RTTI。
除了 C 库头文件的 C++ 包装器(例如 <cstdio>
)之外,没有标准库支持。如果您需要 STL,则应使用此页面上介绍的其他选项之一。
none
还可以选择不使用 STL。在这种情况下,没有链接或许可要求。没有可用的 C++ 标准头文件。
选择 C++ 运行时
CMake
CMake 的默认值为 c++_static
。
您可以使用模块级 build.gradle
文件中的 ANDROID_STL
变量指定 c++_shared
、c++_static
、none
或 system
。要了解更多信息,请参阅 CMake 中 ANDROID_STL 的文档。
ndk-build
ndk-build 的默认值为 none
。
您可以使用 Application.mk 文件中的 APP_STL
变量指定 c++_shared
、c++_static
、none
或 system
。例如
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