157 lines
7.4 KiB
Markdown
157 lines
7.4 KiB
Markdown
# 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++ 标准库,主要需要做下面几个步骤:
|
||
|
||
1. 确定需要使用的 C++ 特性,选择对应的 C++ 标准库
|
||
2. 编译参数添加标准库的搜索路径,例如 `-I ${CXXDIR}/large/include/c++/v1`。注意需要将其添加到 C 标准库的搜索路径之前,即 `-I /opt/jieli/pi32v2/include` 之前。
|
||
3. 链接参数中,需要添加 `libc++.a`, `libc++abi.a` 的路径。
|
||
- 如果使用了异常,还需要增加 `libunwind.a`。且链接参数增加 `--eh-frame-hdr`
|
||
- 如果使用了 TLS 变量,还需要增加 `libemutls.a`(且需要依赖 `pthread`)
|
||
4. 链接参数建议加上 `--gc-sections` 以缩减体积。
|
||
5. 处理全局构造函数,参考下面注意事项。
|
||
6. 一些函数可能在 C 库中确实(未提供,例如 `_malloc_r`, `sscanf` 等)。需要在 SDK 中补充实现。
|
||
|
||
## 注意事项
|
||
|
||
1. 我们使用的是 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__`,一些函数会缺少声明)。
|
||
|
||
2. clang 编译 `.c` 后缀的时候,是 C 编译器。
|
||
编译 `.cxx`, `.cc`, `.cpp` 文件的时候,是当做 C++ 模式处理。
|
||
所以注意当前文件后缀是否正确。
|
||
|
||
3. C++ 标准库使用的版本是 llvm-12.0.1:
|
||
|
||
- 不支持的部分:
|
||
`filesystem`
|
||
`std::cin`, `std::cout`
|
||
`fstream` 相关操作
|
||
|
||
- 支持的部分:
|
||
STL(`vector`, `list`, `map`, `functional`, `algorithm` 等)
|
||
`thread`, `mutex`, `future` 等 (依赖于 `pthread` 接口实现)
|
||
`strstream` 等(依赖于 `locale`)
|
||
|
||
4. 如果要使用 TLS 变量,即 `thread_local` 以及 `__thread` 等变量。
|
||
则需要编译参数加上 `-femulated-tls`,链接参数加上 `--plugin-opt=-emulated-tls`。
|
||
链接库需要加上 `libemutls.a`,且依赖 `pthread` 接口。
|
||
|
||
5. 可以支持异常,异常使用的是基于 dwarf (`.eh_frame`)的处理方式,也就是编译器静态生成栈帧回退的数据结构,在抛出异常时候才会使用。
|
||
|
||
链接脚本需要增加下面的部分:
|
||
|
||
```ld
|
||
.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。
|
||
|
||
会增加较多的额外代码以及数据量,固件体积会增加较多。一般情况下,不推荐使用异常。
|
||
|
||
6. 一般情况下,推荐关闭异常,以及 RTTI:`-fno-exceptions`, `-fno-rtti`
|
||
|
||
如果使用了 `dynamic_cast`,则需要保留 RTTI,不能使用 `-fno-rtti`
|
||
|
||
7. 头文件引用顺序
|
||
1. 注意,先搜索 C++ 头文件,再搜索 C 的头文件。避免冲突。也就是,先 -I C++ 的标准库头文件路径,再 -I C 的标准库头文件路径
|
||
2. 如果用了 `pthread`,则注意不能使用到 C 标准库里面的 `pthread.h`。建议直接弃用系统路径的 C 标准库(拷贝一份到 SDK,并删除其中的 `pthread.h`)
|
||
3. `pthread` 需要使用 `simple_pthread` 文件夹中提供的部分。这个里面的 `pthread.h` 和 `semaphore.h` 是手动将 OS 相关的内容提取出来,生成的头文件,保证了较少的外部依赖(`clang -E -p` 参数进行宏展开后,再手动拷贝相关函数声明以及结构体定义)
|
||
|
||
8. 当需要使用 `iostream`, `strstream` 等相关的内容时候,需要依赖于 `locale` 相关的 api
|
||
如果有报错 `error "Localization is not supported by this configuration of libc++"` 则说明需要换一个 libc++ 的库
|
||
|
||
9. 处理全局变量的构造和析构。
|
||
|
||
链接脚本添加:
|
||
|
||
```ld
|
||
// 全局变量的构造函数列表
|
||
___ctors_begin = .;
|
||
KEEP(*(SORT(.ctors.*))) // 注意要有 KEEP
|
||
KEEP(*(.ctors)) // 注意要有 KEEP
|
||
___ctors_end = .;
|
||
```
|
||
|
||
在 main 函数调用前,调用全局变量的构造函数:
|
||
|
||
```c
|
||
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` 函数:
|
||
|
||
```c
|
||
// 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` 定义:
|
||
|
||
```c
|
||
// 这个在调用 __cxa_atexit 的时候需要用到
|
||
void *__dso_handle = &__dso_handle;
|
||
```
|
||
|
||
10. `new`, `delete`, `__cxa_pure_virtual`, `__cxa_deleted_virtual` 这些函数在 `libc++abi.a` 中已经定义
|