杰理 SDK 文档中心
首页
首页
  • SDK 概述与快速开始

    • SDK 概览与 AC791N 芯片平台
    • 环境搭建与编译指南
    • 烧录与固件升级
    • 工程结构导览
  • 产品方案应用

    • WiFi 摄像头方案
    • WiFi IPC 可视对讲方案
    • WiFi 故事机方案
    • 扫码枪 HID 方案
    • 开发板示例工程
  • 公共应用组件

    • 语音识别 ASR 引擎
    • LLM 与 AI 语音助手接入
    • 摄像头传感器驱动
    • UI 显示框架与驱动
    • USB 主机与设备栈
    • 文件系统与存储管理
    • 系统服务与外设管理
    • 生产测试与射频工具
  • 蓝牙协议栈

    • 经典蓝牙 BR/EDR
    • BLE 低功耗蓝牙
    • 蓝牙 Mesh 网络
    • 蓝牙扩展协议(RCSP/广播/无线麦克风)
  • WiFi 与网络协议栈

    • WiFi 驱动与网络模式
    • lwIP TCP/IP 协议栈
    • 网络安全与加密库
    • 应用层网络协议
    • 流媒体与音视频传输
    • 云平台接入 SDK
    • P2P 远程访问与设备互联
  • 芯片平台与驱动

    • wl82 平台与硬件加速
    • 外设驱动框架
    • 平台配置与固件打包工具
  • 媒体与音频引擎

    • 音频编解码与音源
    • 音效处理引擎
    • 视频与图像处理
  • 操作系统与运行时

    • 实时操作系统与 POSIX 层
    • C/C++ 运行时库
  • 开发资源与文档

    • 文档与规格书
    • 公共示例工程
    • UI 资源工程与打包
    • SDK 辅助工具与脚本

C/C++ 运行时库

AC79xx AIoT SDK 中面向 pi32v2 处理器(杰理 AC79 系列)的 C/C++ 运行时环境:包括基于 clang/LLVM 工具链的 C 标准库补充实现、libc++ 标准库的多配置变体、异常处理(Dwarf .eh_frame)、TLS 支持以及全局构造/析构机制,以及将这些运行时组件集成进固件的完整步骤与注意事项。

Purpose and Scope

本文档说明 SDK 中 C/C++ 运行时库的组成、变体选择、集成方法与底层机制,覆盖:

  • C 运行时:C 标准库中缺失函数(如 _malloc_r)的 SDK 补充实现
  • C++ 运行时:libc++.a、libc++abi.a、libunwind.a、libemutls.a 各库的作用与 16 种编译配置变体
  • 编译器与语言标准支持范围(clang 4.0.1、C++11/14、部分 C++17)
  • 异常处理(.eh_frame / .eh_frame_hdr)与 TLS(emulated TLS)的链接脚本与编译参数
  • 全局构造函数/析构函数的启动流程(__cxa_atexit / __cxa_finalize / __dso_handle)
  • 链接脚本(.ld)中与运行时相关的段定义(.ctors、.eh_frame 等)

与本文档相关的其他目录页(不在本文范围):内存布局与链接脚本的完整细节属于系统启动/链接主题;simple_pthread 的 OS 适配接口属于线程库主题;具体驱动库的段划分见 include_lib/driver 下的 .ld 文件。

Overview

SDK 的 C/C++ 运行时不是单一库,而是一组按需组合的组件。设计上最重要的取舍是体积与特性之间的平衡:嵌入式固件的 Flash/RAM 预算有限,而 C++ 标准库的某些特性(异常、locale、线程、长跳转)会显著增大代码体积。为此 SDK 将 libc++ 编译为多种配置变体(见 include_lib/c++/README.md),让开发者只链接自己实际用到的特性组合。

同时,由于工具链的 C 库并不完整(例如缺少 _malloc_r、sscanf 等重入版本函数),SDK 在 apps/common/system/init_expand.c 中提供了 C 库补充实现,并配套实现 C++ ABI 所需的 __cxa_atexit 等符号。

典型使用场景:

  • 在纯 C 工程中启用少量 C++ 代码(STL 容器、std::string 等),选择无异常/无线程的最小变体
  • 需要多线程 C++ 应用(std::thread、std::mutex、std::future),选择带 pthread 的变体
  • 需要 C++ 异常处理或 TLS 变量时,额外链接 libunwind.a / libemutls.a 并修改链接脚本

Architecture

flowchart TD
    subgraph sg_User["应用层 (Application)"]
        App["用户代码<br/>(.c / .cxx / .cpp)"]
    end

    subgraph sg_Toolchain["编译工具链 (Toolchain)"]
        Clang["clang 4.0.1<br/>pi32v2 后端"]
        Llvm["LLVM 12.0.1<br/>libc++ 头文件"]
    end

    subgraph sg_Runtime["运行时库 (Runtime Libraries)"]
        LibC["C 库 + SDK 补充实现<br/>(init_expand.c)"]
        LibCpp["libc++.a"]
        Abi["libc++abi.a"]
        Unwind["libunwind.a (可选)"]
        Emutls["libemutls.a (可选)"]
        Pthread["simple_pthread (可选)"]
    end

    subgraph sg_Link["链接与启动 (Link & Startup)"]
        LdScript["链接脚本 (.ld)<br/>.ctors / .eh_frame / .eh_frame_hdr"]
        Ctors["全局构造/析构<br/>__cxa_atexit / __cxa_finalize"]
    end

    App --> Clang
    Clang --> Llvm
    Clang --> LibCpp
    Clang --> Abi
    Clang --> LibC
    LibCpp --> Abi
    LibCpp -.->|"启用异常"| Unwind
    LibCpp -.->|"启用 TLS"| Emutls
    LibCpp -.->|"启用线程"| Pthread
    LdScript --> LibC
    LdScript --> LibCpp
    Ctors --> LdScript
    Ctors --> Abi

架构说明:

  • 应用层通过 clang 编译器编译,.c 文件按 C 模式、.cxx/.cc/.cpp 文件按 C++ 模式处理(见 README L51-L53)。
  • 工具链:编译器为 clang 4.0.1(支持到 C++14 及部分 C++17),libc++ 标准库基于 LLVM 12.0.1。
  • 运行时库是分层组合关系:libc++.a 依赖 libc++abi.a(提供 new/delete/__cxa_pure_virtual 等 ABI 符号);异常、TLS、线程特性分别需要额外链接 libunwind.a、libemutls.a、simple_pthread。
  • 链接与启动:链接脚本负责收集 .ctors、.eh_frame、.eh_frame_hdr 等运行时段,启动代码在调用 main() 前逆序执行全局构造函数,并在退出时通过 __cxa_finalize 触发析构。

C++ 标准库变体(Variant Matrix)

libc++ 的每种特性组合都会影响固件体积:例如启用异常会引入基于 .eh_frame 的栈回退数据与 libunwind.a,启用长跳转(large-program)会放大库函数体积。SDK 为此编译了 16 种配置变体,按四个维度组合(见 README L7-L26):

长跳转线程 (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

设计意图:将特性开关前置到库的编译期,使链接期只保留需要的代码路径。median 系列是默认推荐(无任何可选特性);large 系列通过 -mllvm -pi32v2-large-program=true 启用长跳转,允许 call 更远距离的函数,代价是库函数体积增大(见 README L5)。

libc++ 支持范围(基于 LLVM 12.0.1,见 README L55-L65):

  • ✅ 支持:STL(vector、list、map、functional、algorithm 等);thread、mutex、future(依赖 pthread 接口);strstream(依赖 locale)
  • ❌ 不支持:filesystem、std::cin、std::cout、fstream 相关操作

集成步骤(Integration Steps)

README 给出了把 C++ 标准库集成到固件的 6 步流程(见 README L28-L39):

  1. 选择变体:根据所需 C++ 特性(线程/异常/locale/长跳转)确定对应的 libc++ 库
  2. 配置头文件搜索路径:将 C++ 标准库头文件路径加在 C 标准库路径之前,例如 -I ${CXXDIR}/large/include/c++/v1 需先于 -I /opt/jieli/pi32v2/include
  3. 链接参数添加库路径:链接 libc++.a、libc++abi.a;启用异常时再加 libunwind.a 与 --eh-frame-hdr;使用 TLS 变量时再加 libemutls.a(且依赖 pthread)
  4. 缩减体积:建议加 --gc-sections 以丢弃未引用的段
  5. 处理全局构造函数:按"注意事项 9"在链接脚本中收集 .ctors 段并在 main() 前调用
  6. 补充缺失的 C 库函数:_malloc_r、sscanf 等函数可能在 C 库中缺失,需在 SDK 中补充实现

头文件搜索顺序的关键性:C++ 头文件必须优先于 C 头文件,否则会出现声明冲突;若使用 pthread,不能使用 C 标准库中的 pthread.h,而应使用 simple_pthread 文件夹提供的版本——该头文件是通过 clang -E -p 宏展开后手动提取生成的,仅含必要声明与结构体,外部依赖最少(见 README L108-L111)。

编译参数与语言标准

工具链为 clang 4.0.1,支持的语言标准及推荐写法(见 README L43-L49):

标准编译选项
C++11-std=c++11 / -std=gnu++11
C++14-std=c++14 / -std=gnu++14
C++17(部分)-std=c++1z / -std=gnu++1z

设计意图:建议统一使用 gnu++1n 写法,因为 -std=c++1n(不带 gnu)会定义 __STRICT_ANSI__ 宏,导致部分函数缺少声明而编译失败。此外 .c 后缀文件按 C 编译、.cxx/.cc/.cpp 后缀按 C++ 编译,文件后缀选择直接影响语言模式(见 README L51-L53)。

体积与特性推荐:一般推荐关闭异常与 RTTI(-fno-exceptions、-fno-rtti);若使用 dynamic_cast 则必须保留 RTTI(见 README L104-L106)。

异常处理机制(Exception Handling)

SDK 的 C++ 异常采用 Dwarf 静态栈回退方案:编译器在编译期静态生成栈帧回退数据结构(.eh_frame),仅在抛出异常时才实际使用。其工作机制(见 README L71-L102):

  1. 链接脚本必须保留 .gcc_except_table、.eh_frame、.eh_frame_hdr 段,且 .eh_frame/.eh_frame_hdr 需 KEEP() 防止被垃圾回收
  2. 链接参数加 --eh-frame-hdr 才会生成 .eh_frame_hdr 段(用于异常处理时的快速查找)
  3. 编译 C 代码时需加 -funwind-tables,避免异常穿越 C 函数时无法正确回滚栈帧
  4. 额外链接 libunwind.a
  5. 异常数据为只读,可放 Flash(也可放 RAM)
flowchart TD
    Throw["throw 异常"] --> LibUnwind["libunwind.a<br/>解析 .eh_frame_hdr"]
    LibUnwind --> Lookup{"查找 handler<br/>(栈回退信息)"}
    Lookup -->|"找到"| UnwindStack["逐帧回退栈<br/>调用析构函数"]
    UnwindStack --> Catch["进入 catch 块"]
    Lookup -->|"未找到"| Terminate["std::terminate()"]
    Catch --> End([异常处理结束])

代价提示:异常会显著增加固件体积(额外的代码与数据量),非必要场景不推荐使用。

TLS 支持(Emulated TLS)

thread_local 与 __thread 变量通过 emulated TLS 实现(见 README L67-L69):

  • 编译参数:-femulated-tls
  • 链接参数:--plugin-opt=-emulated-tls
  • 链接库:libemutls.a,且依赖 pthread 接口

全局构造与析构(Global Constructors/Destructors)

C++ 全局对象的构造必须在 main() 之前完成,析构在退出时触发。SDK 采用标准的 .ctors 段 + __cxa_atexit 机制(见 README L116-L154):

  1. 链接脚本收集构造函数指针到 ___ctors_begin ~ ___ctors_end 区间(必须 KEEP())
  2. 启动代码在调用 main() 前逆序遍历该区间并逐个调用
  3. 构造函数内通过 __cxa_atexit 注册析构函数,退出时由 __cxa_finalize 触发
  4. 需要提供 __dso_handle 符号(通常定义为其自身地址)
sequenceDiagram
    participant Boot as 启动代码 (_start)
    participant Ctors as 全局构造函数列表
    participant Main as main()
    participant Abi as libc++abi / init_expand.c

    Boot->>Boot: 初始化段 (.data/.bss)
    Boot->>Ctors: 逆序遍历 ___ctors_begin → ___ctors_end
    Ctors->>Abi: __cxa_atexit(func, arg, __dso_handle)
    Abi-->>Ctors: 注册成功 (返回 0)
    Ctors->>Main: 调用 main()
    Main-->>Ctors: 返回/退出
    Ctors->>Abi: __cxa_finalize(__dso_handle)
    Abi->>Abi: 逆序执行已注册的析构函数

C 运行时补充实现(init_expand.c)

工具链自带 C 库并不完整,README 明确指出 _malloc_r、sscanf 等函数缺失,需要在 SDK 中补充实现(见 README L39)。SDK 在 apps/common/system/init_expand.c 中提供了这些补充:

  • __cxa_atexit(init_expand.c L24):注册全局/静态对象的析构函数,供 C++ 运行时在程序退出时逆序调用
  • __dso_handle(init_expand.c L36):声明为 hidden 可见性的全局符号,作为 __cxa_atexit 的 dso 参数
  • _malloc_r(init_expand.c L112):重入版本的内存分配接口,供 libc/libc++ 内部调用

该文件将 C++ ABI 所需的 C 库支撑函数与内存分配整合到系统初始化路径中,保证链接期符号完整。

使用示例(Usage Examples)

示例 1:链接脚本中的异常段定义

异常处理依赖链接脚本保留 .eh_frame 与 .eh_frame_hdr 段,且必须使用 KEEP() 防止被 --gc-sections 回收:

.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 = .;
}

Source: README.md

示例 2:链接脚本中的全局构造函数段

// 全局变量的构造函数列表
___ctors_begin = .;
KEEP(*(SORT(.ctors.*))) // 注意要有 KEEP
KEEP(*(.ctors))         // 注意要有 KEEP
___ctors_end = .;

Source: README.md

示例 3:main() 前调用全局构造函数

typedef void (*pfunc) ();
extern pfunc ___ctors_begin[];
extern pfunc ___ctors_end[];
pfunc *p;

// 调用全局构造函数(注意是要逆序)
for (p = ___ctors_end; p > ___ctors_begin; )
  (*--p) (); // 注意这里是倒序调用的

Source: README.md

示例 4:__cxa_atexit / __dso_handle 支撑符号

// 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);
// 这个在调用 __cxa_atexit 的时候需要用到
void *__dso_handle = &__dso_handle;

Sources:

  • README.md
  • README.md

配置选项(Configuration Options)

配置项类型默认/推荐说明
-std=gnu++11/14/1z编译参数gnu++ 系列语言标准;避免 __STRICT_ANSI__ 导致声明缺失
-mllvm -pi32v2-large-program=true编译参数关长跳转模式,允许 call 更远函数,库体积增大
-I ${CXXDIR}/.../include/c++/v1头文件路径必须位于 C 库路径之前C++ 头文件优先搜索,避免冲突
-femulated-tls编译参数关启用 TLS 变量(thread_local/__thread)
--plugin-opt=-emulated-tls链接参数关与 -femulated-tls 配套
--eh-frame-hdr链接参数仅启用异常时生成 .eh_frame_hdr 段
-funwind-tables编译参数仅启用异常时C 代码也生成栈回退表,保证异常穿越 C 帧正确回滚
-fno-exceptions / -fno-rtti编译参数推荐开启关闭异常/RTTI 以缩减体积;dynamic_cast 需保留 RTTI
--gc-sections链接参数建议开启丢弃未引用段,缩减固件体积
libc++.a + libc++abi.a链接库必选C++ 标准库与 ABI 支撑
libunwind.a链接库仅启用异常时异常栈回退实现
libemutls.a链接库仅启用 TLS 时emulated TLS 实现,依赖 pthread
simple_pthread头文件/库仅启用线程时替代 C 库中的 pthread.h,外部依赖最少

API 参考(API Reference)

int __cxa_atexit(void (*func)(void *), void *arg, void *dso_handle)

注册程序退出时要调用的析构函数(C++ ABI 标准接口)。

参数:

  • func (void (*)(void *)): 析构函数指针
  • arg (void *): 传给析构函数的参数(通常是对象指针)
  • dso_handle (void *): 动态共享对象句柄,通常传 &__dso_handle

返回: 成功返回 0。

SDK 实现位置: apps/common/system/init_expand.c L24

void __cxa_finalize(void *f)

触发执行已注册的析构函数;传入 NULL 时执行所有已注册函数。

SDK 提供方式: 见 README.md L143-L147

void *__dso_handle

全局对象句柄符号,供 __cxa_atexit 使用;SDK 中定义为 void *__dso_handle = &__dso_handle;(init_expand.c L36)。

void *_malloc_r(size_t sz)

C 库重入版本的内存分配函数,供 libc/libc++ 内部使用;SDK 补充实现位于 init_expand.c L112。

new / delete / __cxa_pure_virtual / __cxa_deleted_virtual

这些符号已在 libc++abi.a 中定义,无需应用层额外实现(见 README L156)。

失败模式、边界情况与并发

链接期失败模式

  • 符号缺失:_malloc_r、sscanf 等 C 库函数缺失会导致链接失败。解决方式是使用 SDK 的 init_expand.c 补充实现(README 集成步骤第 6 条明确列出)。
  • .eh_frame 被回收:若链接脚本未对 .eh_frame/.eh_frame_hdr 使用 KEEP(),--gc-sections 会将其丢弃,异常在运行时无法回退栈帧,直接导致 std::terminate()。
  • .ctors 被回收:未 KEEP(*(SORT(.ctors.*))) 时全局对象不会被构造,C++ 全局状态静默失效——这类错误极难排查,因此 README 反复强调 KEEP 的必要性。

编译期失败模式

  • __STRICT_ANSI__ 宏:使用 -std=c++1n(不带 gnu)时部分函数缺少声明而编译报错;应改用 gnu++1n。
  • 头文件搜索顺序错误:C++ 头文件必须位于 C 标准库头文件之前,否则声明冲突;使用 pthread 时不得引用 C 库自带的 pthread.h,否则与 simple_pthread 的定义冲突。
  • locale 不匹配:使用 iostream/strstream 时若编译报 error "Localization is not supported by this configuration of libc++",说明所选变体未包含 locale 支持,需更换为带 locale 的库变体。

运行时边界情况

  • 异常穿越 C 代码:C 函数默认不生成栈回退表,异常穿越 C 帧时无法回滚;必须在 C 编译选项中加 -funwind-tables。
  • dynamic_cast 与 RTTI:关闭 RTTI(-fno-rtti)后 dynamic_cast 无法工作,需要保留 RTTI 或改用其他类型判别方案。
  • 并发:std::thread/std::mutex/std::future 依赖 pthread 接口实现;选择带 pthread 的变体并链接 simple_pthread 是线程安全的先决条件。TLS 变量(thread_local)依赖 emulated TLS 与 pthread 配合,二者必须同时启用。

性能与运维注意事项

  • 体积优先原则:默认选用 median 系变体;仅在确实需要时启用线程/异常/locale/长跳转。异常会"增加较多的额外代码以及数据量,固件体积会增加较多"(README 明确不建议默认使用)。
  • --gc-sections 应始终开启,配合链接脚本中的 KEEP() 精确控制运行时段保留。
  • 异常段放置:.eh_frame 与 .eh_frame_hdr 为只读数据,可放置于 Flash,不占 RAM。
  • 长跳转取舍:large 系列允许更远距离的 call,但库函数体积更大——适用于代码总量超过短跳转寻址范围的大工程。
  • 全局构造顺序:构造函数按 .ctors 段逆序执行(栈式语义),析构顺序与之相反,符合 C++ 语言规范。

扩展点(Extension Points)

  • 补充 C 库函数:apps/common/system/init_expand.c 是 C 库补充实现的挂载点,新增缺失的 C 库符号(如格式化、重入内存接口)可在此文件扩展。
  • __cxa_atexit 注册机制:应用可通过该接口注册自定义退出清理函数,实现"延迟注册、退出时逆序执行"的资源清理模式。
  • 链接脚本定制:include_lib/**/*.ld(如 include_lib/driver/cpu/wl82/system.ld、system_data.ld)定义了系统内存布局;新增运行时段(异常、TLS 相关段)需在此类脚本中同步扩展。
  • 变体选择:若现有 16 种 libc++ 变体不满足需求(如自定义特性组合),可参照 -mllvm -pi32v2-large-program=true 等编译选项重新编译 libc++ 变体并加入链接路径。

相关链接(Related Links)

  • include_lib/c++/README.md(C++ 运行时库官方说明)
  • apps/common/system/init_expand.c(C 库补充实现)
  • include_lib/driver/cpu/wl82/system.ld(系统链接脚本)
  • include_lib/driver/cpu/wl82/system_data.ld(数据段链接脚本)
  • 线程库(simple_pthread)与系统启动流程的细节,见对应目录页:操作系统运行时、系统启动与内存布局。
Prev
实时操作系统与 POSIX 层