7.4 KiB
C++ 代码使用说明
C++ 标准库有一些特性会占用较多的空间。一些情况下,我们可能并不会使用,为了兼顾程序的体积,C++ 标准库编译了各种不同的配置。
长跳转是指编译时,添加了 -mllvm -pi32v2-large-program=true。这个使得可以call更远距离的函数(同时生成的库函数体积也会更大一些)。
C++ 库列表说明
| 长跳转 | 线程 (thread) | 异常(exception) | locale | 名称 |
|---|---|---|---|---|
| 未支持 | 未支持 | 未支持 | 未支持 | median |
| 未支持 | 未支持 | 未支持 | 支持 | locale-median |
| 未支持 | 支持 | 未支持 | 未支持 | pthread-median |
| 未支持 | 支持 | 未支持 | 支持 | pthread-locale-median |
| 未支持 | 未支持 | 支持 | 未支持 | exception-median |
| 未支持 | 未支持 | 支持 | 支持 | exception-locale-median |
| 未支持 | 支持 | 支持 | 未支持 | exception-pthread-median |
| 未支持 | 支持 | 支持 | 支持 | exception-pthread-locale-median |
| 支持 | 未支持 | 未支持 | 未支持 | large |
| 支持 | 未支持 | 未支持 | 支持 | locale-large |
| 支持 | 支持 | 未支持 | 未支持 | pthread-large |
| 支持 | 支持 | 未支持 | 支持 | pthread-locale-large |
| 支持 | 未支持 | 支持 | 未支持 | exception-large |
| 支持 | 未支持 | 支持 | 支持 | exception-locale-large |
| 支持 | 支持 | 支持 | 未支持 | exception-pthread-large |
| 支持 | 支持 | 支持 | 支持 | exception-pthread-locale-large |
集成的步骤
为了集成 C++ 标准库,主要需要做下面几个步骤:
- 确定需要使用的 C++ 特性,选择对应的 C++ 标准库
- 编译参数添加标准库的搜索路径,例如
-I ${CXXDIR}/large/include/c++/v1。注意需要将其添加到 C 标准库的搜索路径之前,即-I /opt/jieli/pi32v2/include之前。 - 链接参数中,需要添加
libc++.a,libc++abi.a的路径。- 如果使用了异常,还需要增加
libunwind.a。且链接参数增加--eh-frame-hdr - 如果使用了 TLS 变量,还需要增加
libemutls.a(且需要依赖pthread)
- 如果使用了异常,还需要增加
- 链接参数建议加上
--gc-sections以缩减体积。 - 处理全局构造函数,参考下面注意事项。
- 一些函数可能在 C 库中确实(未提供,例如
_malloc_r,sscanf等)。需要在 SDK 中补充实现。
注意事项
-
我们使用的是 clang 4.0.1,可以支持到 c++14 以及部分的 c++17 分别是:
-std=c++11,-std=gnu++11# c++11-std=c++14,-std=gnu++14# c++14-std=c++1z,-std=gnu++1z# c++17 (部分)
建议都使用 gnu++1n 的写法。这样不会定义
__STRICT_ANSI__宏,避免问题(如果定义了__STRICT_ANSI__,一些函数会缺少声明)。 -
clang 编译
.c后缀的时候,是 C 编译器。 编译.cxx,.cc,.cpp文件的时候,是当做 C++ 模式处理。 所以注意当前文件后缀是否正确。 -
C++ 标准库使用的版本是 llvm-12.0.1:
-
不支持的部分:
filesystemstd::cin,std::coutfstream相关操作 -
支持的部分: STL(
vector,list,map,functional,algorithm等)thread,mutex,future等 (依赖于pthread接口实现)strstream等(依赖于locale)
-
-
如果要使用 TLS 变量,即
thread_local以及__thread等变量。 则需要编译参数加上-femulated-tls,链接参数加上--plugin-opt=-emulated-tls。 链接库需要加上libemutls.a,且依赖pthread接口。 -
可以支持异常,异常使用的是基于 dwarf (
.eh_frame)的处理方式,也就是编译器静态生成栈帧回退的数据结构,在抛出异常时候才会使用。链接脚本需要增加下面的部分:
.text : { *(.text*) *(.gcc_except_table*); } .eh_frame : { __eh_frame_start = .; KEEP(*(.eh_frame)); // 注意要有 KEEP __eh_frame_end = .; } .eh_frame_hdr : { __eh_frame_hdr_start = .; KEEP(*(.eh_frame_hdr)); // 注意要有 KEEP __eh_frame_hdr_end = .; }在运行时,需要把
.eh_frame,.eh_frame_hdr放到 flash 中(也可内存,这两部分数据是只读的)链接参数还需要加上
--eh-frame-hdr这样才会生成.eh_frame_hdr段。用以异常处理。 编译 C 代码时,需要加上-funwind-tables,避免在 C 代码部分异常未能正确回滚栈帧。需要额外链接 libunwind.a。
会增加较多的额外代码以及数据量,固件体积会增加较多。一般情况下,不推荐使用异常。
-
一般情况下,推荐关闭异常,以及 RTTI:
-fno-exceptions,-fno-rtti如果使用了
dynamic_cast,则需要保留 RTTI,不能使用-fno-rtti -
头文件引用顺序
- 注意,先搜索 C++ 头文件,再搜索 C 的头文件。避免冲突。也就是,先 -I C++ 的标准库头文件路径,再 -I C 的标准库头文件路径
- 如果用了
pthread,则注意不能使用到 C 标准库里面的pthread.h。建议直接弃用系统路径的 C 标准库(拷贝一份到 SDK,并删除其中的pthread.h) pthread需要使用simple_pthread文件夹中提供的部分。这个里面的pthread.h和semaphore.h是手动将 OS 相关的内容提取出来,生成的头文件,保证了较少的外部依赖(clang -E -p参数进行宏展开后,再手动拷贝相关函数声明以及结构体定义)
-
当需要使用
iostream,strstream等相关的内容时候,需要依赖于locale相关的 api 如果有报错error "Localization is not supported by this configuration of libc++"则说明需要换一个 libc++ 的库 -
处理全局变量的构造和析构。
链接脚本添加:
// 全局变量的构造函数列表 ___ctors_begin = .; KEEP(*(SORT(.ctors.*))) // 注意要有 KEEP KEEP(*(.ctors)) // 注意要有 KEEP ___ctors_end = .;在 main 函数调用前,调用全局变量的构造函数:
typedef void (*pfunc) (); extern pfunc ___ctors_begin[]; extern pfunc ___ctors_end[]; pfunc *p; // 调用全局构造函数(注意是要逆序) for (p = ___ctors_end; p > ___ctors_begin; ) (*--p) (); // 注意这里是倒序调用的需要添加
__cxa_atexit,__cxa_finalize函数:// https://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/baselib---cxa-atexit.html int __cxa_atexit(void (*func) (void *), void * arg, void * dso_handle); void __cxa_finalize(void *f);需要添加
__dso_handle定义:// 这个在调用 __cxa_atexit 的时候需要用到 void *__dso_handle = &__dso_handle; -
new,delete,__cxa_pure_virtual,__cxa_deleted_virtual这些函数在libc++abi.a中已经定义