GNU同C++STL的区别与联系
调研场景:
应用背景:
Andrid NDK 一共提供三种类型的c++运行时库
- system
system 运行时指的是 /system/lib/libstdc++.so。
非完全stl,完全stl需使用上面的libc++。这是与Android发布绑定的库。
System运行库指的是Android版本里的/system/lib/libstdc++.so,提供基 本的c++运行支持, 提供new/delete支持,仅提供c标准库的c++封装,比如cstdio - None
没有标准库支持 - 可供自由选择的STL
gnustl、libc++、STLport
重点弄清gnu与libc++stl的一些区别:
gnu与libc++的stl都是stl的不同实现,其接口规范都是一致的,但是内部实现不同
简要介绍:
1. gnustl
1.1 简介
gnustl: Android上的GNU C++库,对应GNU/Linux系统中的libstdc++,gnustl是基于SGI的stl基础上进行的开发。
这个库和GCC仅仅绑定,但是后期不再更新,最新NDK不再支持
此库和Clang存在部分冲突
Note: 新版NDK将会删除此库,从NDK16开始,被libc++替代。
- 静态库:libgnustl_static.a
- 动态库:libgnustl_shared.so
1.2应用
只能在Application.mk设置,鉴于gnustl_share存在诸多不稳定问题,所以尽量采用gnustl_static替代gnustl_share
(1)gnustl_share : 当使用static library时,相关代码被连接到编译输出库中,这会造成目标文件变大。
APP_STL := gnustl_shared
优点:
目标文件已经包含所依赖的代码,不受运行环境的影响。
缺点:
目标文件变大,不利于复用
优缺点同标准静态连接库相同。
(2)gnustl_static
动态链接库:依赖代码不编译到目标文件中。
APP_STL := gnustl_static
优点:
目标文件体积小。
缺点:
需要动态链接依赖库,由于gnustl是系统库,所以不需要额外输出libgnustl_share.so。但是由于Android系统的碎片化,各个版本的libgnustl_share.so不一致,就导致崩溃或者运行异常问题。
2.libc++
2.1 简介
libc++:是LLVM c++标准库。从NDK r18之后是唯一的STL
NDK开发无论是静态库还是动态库,libc++都是用NDK里的发布版本打包在应用里:
动态库直接在apk里带上libc++_shared.so;
静态库已经把程序需要的STL的代码直接打到应用程序或其所用的native库里。
port从 r18开始从NDK中被移除)
- 动态库: libc++_shared.so
- 静态库: libc++_static.a
2.2应用
(1). lib++_shared
APP_STL := c++_shared
libc++ 不是系统库。如果使用 libc++_shared.so,必须将其包含在 APK 中。如果使用 Gradle构建应用,此步骤会自动完成。
共享运行时
如果应用包括多个共享库,应使用 libc++_shared.so。 在 Android 系统中,NDK 使用的 libc++ 与操作系统的 libc++ 是不同的。如此一来,即使应用以旧版 Android 为目标平台,NDK 用户也能获得最新的 libc++功能和问题修复。需要权衡的是,如果使用 libc++_shared.so,就必须将其包含在您的 APK 中。如果使用 Gradle构建应用,此步骤会自动完成。 旧版 Android 的 PackageManager和动态链接器存在错误,导致原生库的安装、更新和加载不可靠。具体而言,如果应用以早于 Android 4.3(Android API r18的 Android 版本为目标,并且使用 libc++_shared.so,必须先加载共享库,再加载依赖于共享库的其他库。
ReLinker 项目能够解决所有已知的原生库加载问题,而且相较于自行编写解决方法,它通常是更好的选择。
(2). lib++_static
APP_STL := c++_static
静态运行时
如果应用的所有原生代码均位于一个共享库中,建议使用静态运行时。这样可让链接器最大限度内联和精简未使用的代码,使应用达到最优化状态且文件最小巧。这样做还能避免旧版 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++ 运行时的静态变体。
虽然都是LLVM的c++ STL,此处NDK里的libc++不是Android源码中编译出的c++系
统STL(libc++.so),此处的libc++是基于NDK开发时,NDK中已经编译好的库。如
果NDK开发的应用用到libc++_shared.so, .so会被打包到编译出的APK里;用到
libc++_static.a, .a里被用到的程序会被打到使用者的程序中的。也就是发布应用
时,会带着stl一起发布,不依赖Android版本内部的stl。
3.STLport
由STLport项目编写的C++标准库的第三方实现,自08年以来就不怎么被应用,与gnustl一样STLport被移除
- 静态库:stlport_static.a
- 动态库:stlport_shared.so
4.ndk-build
ndk-build 的默认值为 none。可以使用 Application.mk 文件中的 APP_STL 变量指定
c++_shared、c++static、none 或 system。
ndk-build 仅允许为应用选择一个运行时,并且只能在 Application.mk 中进行选择。
例如:
APP_STL := c++_shared
5.直接使用 clang
如果构建系统中直接使用 clang,则 clang++ 将默认使用 c++_shared。如需使用静态变体,请将
-static-libstdc++ 添加至链接器标志中。请注意,尽管由于历史原因该选项使用的名称是“libstdc++”,但也适用于 libc++
6.clang 与Gcc的简单比较
Clang 是一个 C、C++、Objective-C 和 Objective-C++编程语言的编译器前端,采用底层虚拟机(LLVM)作为后端。至于为什么有了 GCC 还要开发 Clang?
(1)Clang 采用的是 BSD 协议的许可证,而 GCC 采用的是 GPL协议,显然前者更为宽松;
(2)Clang 是一个高度模块化开发的轻量级编译器,编译速度快、占用内存小、有着友好的出错提示
7.Andriod 移除gnustl选择libc++的原因
( 1)、clang库 由于 Android OS 放弃GCC转向了 Clang 编译器, 所以 NDK 将移除GCC, 所以建议用Clang编译程序;同时libc++是针对clang编译器特别重写的C++标准库,而gnustl会和clang库产生冲突
(2)、版权原因 标准的GNU STL是由libstdc++提供的,本身虽然是GPL,但是只要不修改它的代码,就可以自由使用。而在Android平台上,因为很多适配上的问题,不经修改的libstdc++是无法直接使用的,所以NDK无法直接提供
(3)、同时GNU STL功能全但是大,有一些不太常用的功能开销
8、Andriod没有自带STL的原因
C++ ABI兼容性一直无法保证。目前Android NDK 只能保证C API向前向后兼容。C++API的兼容性在官方roadmap中,但尚未排期。那么STL的兼容性保证就要更晚了。相比C API,C++ API保持好ABI兼容性的难度很大。
9.其他的一些注意事项
9.1CMake设置:
CMake 默认设置为 C++ Clang 默认设置的版本(目前为 C++14),需要将 CMakeLists.txt 中的标准CMAKE_CXX_STANDARD 设置为适当的值,才能使用 C++17 或更高版本的功能。
ndk-build 默认情况下仍将此决定留给 Clang,因此 ndk-build 用户应使用 APP_CPPFLAGS 来添加-std=c++17 或任何所需内容
9.2 不要在ndk中使用系统预编译好的C++库
NDK的libc++的C++11命名空间为std::__ndk1 NDK的gnustl的C++11命名空间为std 安卓系统的libc++的C++11命名空间为std::__1
由于命名空间不一样,所以无法在NDK中使用安卓系统预编译好的C++库
源码分析:
gnu stl 详细的英文介绍文档
https://gcc.gnu.org/onlinedocs/libstdc++/
sgi stl 详细的英文介绍文档
http://www.sgi.com/tech/stl/
llvm stl 详细的英文介绍文档
http://libcxx.llvm.org/
stlport 详细的英文介绍文档
http://www.stlport.org/
一些编译问题的解决方案:
一 、gnustl_share崩溃解决方案
统一采用NDK10中的gnustl_share的库,然后在Android.mk和代码中显示加载此库。具体步骤如下:
使用NDKr9c或者r10e
libgnustl_shared.so <= /sources/cxx-stl/gnu-libstdc++/4.8/libs/armeabi/
显示加载复制出来的gnustl_share
System.loadLibrary("gnustl_shared")
二、Android NDK APP_STL gnustl_shared is no longer supported 报错处理
错误原因:APP_STL gnustl_shared 不再被ndk支持。
修改 Applicaiton.mk 中的相关配置
1. APP_STL := gnustl_static 改为 APP_STL := c++_static;
2.删除NDK_TOOLCHAIN or NDK_TOOLCHAIN_VERSION;
对于cmake编译:
1.删除 ANDROID_TOOLCHAIN
对于独立的toolchains
用clang/clang++ binaries 代替 gcc/g++.
补充libc++和libstdc++:
libc++和libstdc++的区别或者比较:
一、lib c++
libc++是针对clang编译器特别重写的C++标准库
libc++ 是 C++ 标准库的实现,面向 C++11、C++14 及更高版本
详细英文介绍 见链接4
优点
ABI 与 gcc 的 libstdc++ 兼容,用于一些低级功能,例如异常对象、rtti 和内存分配
快速执行、占用内存少、快速编译
支持的平台
二、libstdc++
libstdc++针对gcc编译器的c++标准库;
详细英文介绍 见链接5
优点
clang对libstdc++的支持更好,执行速度更快(这一点不一定,只是看到有人这样说)
缺点
libstdc++ 在构建时考虑了效率,因此很少或不执行 C++ 标准不需要的错误检查。这意味着错误地使用 C++
标准库的程序将表现出不可移植的行为,甚至可能无法预测,因为它们会进入特定于实现的或未定义的行为。为了在这些错误出现问题之前检测其中的一些错误,libstdc++提供了一种调试模式,该模式提供了对库设施的额外检查,并且一旦检测到错误,就会通过向标准发出问题描述来报告使用 libstdc++的错误。错误并中止程序。此调试模式可用于 GCC 3.4.0 及更高版本。
三、一些注意事项
不同操作系统使用的本地库有所区别,在GNU / Linux上一般使用libstdc ++,在Mac OS X上一般使用libc ++
二者使用取决于编译器优先集成哪个,一般libstdc++兼容性好
libstdc++跟clang++一起用的时候可能会有各种bug,比如前段时间出过libstdc++的variant不能在clang++上编译,以及最近libstdc++的filesystem::directory_iterator在clang++编译后会提示找不到符号
补充LLVM和Clang 的关系:
Clang 是一个 C、C++、Objective-C 和 Objective-C++ 编程语言的编译器前端,采用底层虚拟机(LLVM)作为后端。
传统编译器
传统编译器的工作原理,基本上都是三段式的,可以分为前端、优化器和后端。前端负责解析源代码,检查语法错误,并将其翻译为抽象的语法树;优化器对这一中间代码进行优化,试图使代码更高效;后端则负责将优化器优化后的中间代码转换为目标机器的代码,这一过程后端会最大化的利用目标机器的特殊指令,以提高代码的性能
LLVM
广义的 LLVM 指的是一个完整的 LLVM 编译器框架系统,包括了前端、优化器、后端、众多的库函数以及很多的模块;而狭义的 LLVM则是聚焦于编译器后端功能的一系列模块和库,包括代码优化、代码生成、JIT 等。
LLVM与clong