
资源介绍
英文电子书)
介绍如何将现代 C++ 应用于嵌入式系统开发的书籍,旨在帮助从 C 语言过渡到现代 C++ 的开发者掌握相关技能。以下是其核心内容总结:
第一部分:嵌入式开发中 C++ 简介
揭开 C++ 的常见误区
历史与发展:C++ 源于 “带类的 C”,经过多年发展已成为支持多种编程范式的现代语言,并非仅在 C 基础上增加类。
代码膨胀与运行时开销:通过分析构造函数、析构函数、模板等特性的汇编输出,说明 C++ 遵循 “零开销原则”,未使用的特性不会带来额外开销,优化后的代码效率很高。
RTTI 与异常:这两个特性可能带来开销,在嵌入式系统中通常禁用,编译器也支持相关禁用选项。
资源受限嵌入式系统的挑战
安全关键与硬实时系统:以安全气囊控制单元为例,说明实时性要求的重要性,介绍了 A-B timing 测量、软件 instrumentation 等性能评估方法。
动态内存管理:分析了内存碎片化问题及安全关键系统中动态内存管理的准则,介绍了 C++ 标准库中动态内存管理的机制及应对策略(如使用std::pmr::monotonic_buffer_resource)。
禁用不必要的 C++ 特性:如通过编译器标志禁用异常(-fno-exceptions)和 RTTI(-fno-rtti),删除new和delete运算符防止动态内存分配。
嵌入式 C++ 生态系统
编译器与开发环境:介绍了 Arm Keil MDK、IAR Embedded Workbench、GCC 等工具链,及其对 C++ 标准的支持和在功能安全领域的应用。
静态分析工具:如 GCC 的-fanalyzer、clang-tidy、cppcheck 等,用于检测代码中的潜在问题。
单元测试:常用框架有 Google Test、Catch2 等,可在主机或目标设备上运行,确保代码功能正确性。
性能分析:利用目标设备的跟踪单元(如 DWT、ITM、ETM)和工具(如 SEGGER SystemView)进行性能分析。
C++ 嵌入式项目开发环境搭建
环境要求:包括编译器(支持 C++23 的 ARM GNU Toolchain)、构建自动化工具(CMake、make)、模拟器(Renode)、代码编辑器(VS Code)等。
容器化开发环境:使用 Docker 构建包含所需工具的容器,结合 VS Code 的 Dev Containers 扩展实现便捷开发。
示例构建:以 “Hello, World!” 程序为例,展示使用 CMake 构建固件、在 Renode 中运行及调试的过程。
第二部分:C++ 基础知识
类 ——C++ 应用的构建块
封装:通过访问说明符(public、private、protected)控制类成员的访问权限,实现代码的模块化和接口清晰化。
存储期与初始化:介绍了非静态成员初始化(默认成员初始化器、构造函数与成员初始化列表)、转换构造函数与explicit说明符、静态成员初始化等。
继承与动态多态:讲解了继承的概念、虚函数的实现(虚表)、纯虚函数与抽象类,以及动态多态在代码设计中的应用(如定义接口类实现灵活的软件架构)。
类之外的基本 C++ 概念
命名空间:用于组织代码,避免命名冲突,支持嵌套命名空间和匿名命名空间。
函数重载:允许同一作用域内的函数同名但参数列表不同,编译器通过名称修饰区分。
与 C 的互操作性:通过extern "C"实现 C 语言链接,C++ 可使用 C 标准库(如等头文件)。
引用:包括左值引用和右值引用,介绍了值类别及引用在函数参数、返回值等场景的使用。
标准库容器与算法:如std::array、std::vector等容器,std::copy_if、std::sort等算法,以及容器适配器(如std::stack、std::priority_queue)。
强化固件 —— 实用的 C++ 错误处理方法
错误码与断言:错误码通过枚举等方式返回,需调用者检查和处理;断言(assert)用于检测开发阶段的编程错误,可通过NDEBUG宏禁用。
异常:介绍了异常处理机制(try/catch),但由于其在嵌入式系统中可能带来的内存和确定性问题,通常禁用。
std::optional与std::expected:std::optional用于可能无返回值的场景,std::expected可同时返回值或错误信息,提供更灵活的错误处理方式。
第三部分:C++ 高级概念
使用模板构建通用且可重用的代码
模板基础:函数模板和类模板的定义与实例化,模板参数推导,模板特化。
模板元编程:利用模板在编译期进行计算,如通过std::enable_if和类型 traits 实现编译期条件判断。
概念(Concepts):C++20 引入,用于约束模板参数,提高代码可读性和编译器错误信息的清晰度。
编译期多态:通过类模板和 CRTP(奇异递归模板模式)实现,避免动态多态的虚函数开销。
通过强类型提高类型安全性
隐式转换:包括数值提升与转换、数组到指针转换、函数到指针转换等,分析了可能带来的问题。
显式转换:介绍了const_cast、static_cast、dynamic_cast、reinterpret_cast的使用场景和注意事项。
类型双关(Type punning):通过std::memcpy或std::bit_cast(C++20)实现安全的类型转换,避免未定义行为。
强类型:通过定义特定类型封装基本类型(如表示不同物理单位的类型),避免错误使用,提高代码的可读性和安全性。
使用 Lambda 编写富有表达力的代码
Lambda 表达式基础:语法结构(捕获子句、参数列表、返回类型、函数体),可捕获周围作用域的变量(按值或按引用)。
使用std::function存储 Lambda:std::function可存储各种可调用对象(包括 Lambda),方便在不同地方传递和调用。
命令模式:结合 Lambda 和std::function实现命令模式,如在 GPIO 中断管理器中存储中断处理函数。
std::function与动态内存分配:std::function可能使用动态内存,可通过小对象优化或使用引用包装器(std::ref)避免。
编译期计算
模板元编程:通过递归模板实现编译期计算(如计算阶乘)。
constexpr说明符:用于变量和函数,使得变量在编译期初始化,函数可在编译期或运行期执行,可用于生成 lookup 表等。
consteval说明符:C++20 引入,指定函数必须在编译期执行,确保计算在编译期完成。
第四部分:将 C++ 应用于解决嵌入式领域问题
编写 C++ HAL
内存映射外设:通过模板实现类型安全的内存映射外设访问,控制寄存器的读写权限,封装位域操作。
定时器:利用模板和 traits 类设计通用的定时器接口,适配不同型号的定时器硬件。
使用 C 库
在 C++ 项目中使用 C HAL:通过适配器模式封装 C 库,定义 C++ 接口,提高代码的灵活性和可测试性。
静态类:将 C 库函数封装到静态类中,通过模板参数传递,便于代码复用和测试。
使用 RAII 包装 LittleFS C 库:利用 RAII 机制管理文件系统资源(如文件的打开与关闭),确保资源正确释放。
用 Sequencer 增强超级循环
超级循环的局限性:随着业务逻辑复杂,超级循环中的标志位增多,代码变得混乱,难以维护和扩展。
Sequencer 设计:基于命令模式,存储和执行任务,支持任务优先级,从 ISR 添加任务,在超级循环中按优先级执行。
实现:使用etl::delegate存储可调用对象,std::priority_queue管理任务,实现高效的任务调度。
实用模式 —— 构建温度发布器
观察者模式:包括发布者和订阅者,发布者维护订阅者列表,在数据更新时通知订阅者。
运行时实现:基于动态多态,订阅者实现统一接口,发布者通过接口指针管理订阅者。
编译期实现:利用变参模板和折叠表达式,在编译期确定订阅者,生成高效代码,避免动态内存和虚函数开销。
设计可扩展的有限状态机(FSM)
简单实现:使用枚举表示状态和事件,通过 switch-case 语句处理状态转换和动作执行。
使用状态模式实现:将每个状态封装为类,通过多态实现状态转换,提高代码的可扩展性。
使用标签分发实现状态模式:利用模板和重载实现编译期的状态转换处理。
Boost SML:介绍了 Boost 状态机库,提供简洁的 DSL 用于定义状态机。
库和框架
标准库:介绍了嵌入式系统中可用的标准库组件(如容器、算法),及应避免使用的部分。
嵌入式模板库(ETL):提供固定大小的容器和工具,适合资源受限的嵌入式系统。
Pigweed:谷歌的嵌入式库,提供 RPC、Protocol Buffers 等功能。
Compile-time Initialization and Build(CIB):用于编译期初始化,简化嵌入式系统的启动过程。
跨平台开发
编写可移植代码的重要性:遵循 SOLID 设计原则,提高代码的可维护性和可移植性。
可测试性:设计易于测试的代码,使用单元测试框架验证功能,确保在不同平台上的正确性。
本书通过大量实例和代码片段,详细讲解了现代 C++ 在嵌入式系统中的应用,涵盖从基础语法到高级特性,从工具链搭建到设计模式应用,适合有 C 基础的嵌入式开发者学习。