现代嵌入式 C++ 教程系列 —— “C++ 一定会导致代码膨胀吗?”
答案是:不一定,甚至在很多情况下不会。
现代 C++(C++11/14/17/20/23)在嵌入式领域已经可以做到零成本抽象(zero-cost abstractions)或极低成本,代码大小膨胀主要取决于你用了哪些特性以及如何使用它们。
下面我们用事实 + 数据 + 工程实践来拆解这个经典误区(2025–2026 年嵌入式圈的主流共识)。
1. 哪些 C++ 特性几乎必然导致膨胀?(主动避开它们)
| 特性 | 典型膨胀幅度 | 为什么膨胀? | 嵌入式推荐做法 |
|---|---|---|---|
| 异常(exceptions) | +10% ~ +100%+ | 展开表、栈回溯信息、personality routine | 关掉:-fno-exceptions |
| RTTI(typeid、dynamic_cast) | +5% ~ +30% | 类型信息表(vtable-like) | 关掉:-fno-rtti |
| 标准库大部分容器(std::vector、std::string 等默认实现) | +5kB ~ +几十kB | 动态分配 + 异常安全 + 通用算法 | 用固定大小容器、etl、C++20 std::span 等 |
| 虚函数(大量、多态深度) | +几百字节 ~ 几kB | vtable + thunks + 间接调用 | 尽量用 CRTP / 模板静态多态 |
| 过度模板实例化(deep template metaprogramming) | 可达 +几十kB | 每个不同类型组合生成一份代码 | 限制模板深度、用 type-erasure 收敛 |
一句话:只要你不打开异常和 RTTI,并且不滥用动态多态和标准库动态容器,现代 C++ 的代码大小通常比等效 C 代码大 0%~15%,甚至更小(因为更好的内联和优化机会)。
2. 哪些现代 C++ 特性是“零成本”或“负成本”的?(强烈推荐用)
| 特性 | 运行时成本 | 代码大小影响 | 典型收益(嵌入式视角) |
|---|---|---|---|
constexpr / consteval | 零 | 通常减小(计算移到编译期) | 寄存器/bit 操作、CRC表、数学常量表生成 |
inline + 小函数 | 零或负 | 经常减小(消除调用) | 取代 C 的宏,类型安全 + 可调试 |
| 模板(适度使用) | 零(优化后) | 可控(看实例化数量) | 固定大小 ring buffer、寄存器访问、HAL 驱动 |
enum class + strong typing | 零 | 几乎无 | 防止误传参数,提升可读性 |
auto + trailing return | 零 | 无 | 写法更简洁,维护性更好 |
| CRTP(静态多态) | 零 | 通常比虚函数小 | 接口 + 实现分离,无 vtable 开销 |
std::array、std::span | 零 | 极小或更小 | 取代 C 风格数组,边界安全 |
| RAII + scope guard | 零(优化后) | 极小 | 自动释放外设、关闭中断等,减少 finally 样板 |
[[no_unique_address]] (C++20) | 零 | 减小 | 空基类优化,减少结构体 padding |
真实案例(常见于 2024–2026 年文章和会议):
- 用模板 + constexpr 写的 GPIO 驱动:比手写 switch-case 的 C 版本小 10–30 字节,可读性高 5 倍。
- 用 CRTP 实现的 HAL:vtable 完全消除,代码大小和 C 相当或略小。
- 用
std::array<uint8_t, N>取代uint8_t buf[64]:大小相同,但编译期边界检查。
3. 现代嵌入式项目中控制代码大小的实用 checklist(2025–2026 主流做法)
- 编译选项(GCC/Clang/ARM Compiler)必加:
-fno-exceptions -fno-rtti -fno-threadsafe-statics
-ffunction-sections -fdata-sections -Wl,--gc-sections # 死代码消除
-Os 或 -Oz # 大小优先优化
-flto # 跨文件内联 + 更好优化
- 模板使用三原则:
- 模板参数尽量少(1–3 个常见)
- 优先用
constexpr if收敛实例化路径 - 超过 3–4 种类型组合时考虑 type-erasure(
std::any/function_ref/ 自写接口)
- 容器策略:
- 小数据 →
std::array/ C 数组 - 中等固定 → etl::vector / freertos::fixed_vector
- 极少动态 → 自己写静态池 + 索引(最安全)
- 链接器脚本 + map 文件分析:
- 每次构建后看
.map/.lst,找出最大函数/段 - 经常是意外实例化的模板或没被 gc 的虚表
4. 真实数据参考(近几年公开项目/文章)
- 很多 Cortex-M0/M3/M4 项目:合理现代 C++ 比纯 C 大 0–12%(关异常/RTTI 后)
- 滥用模板 + STL → 大 30–80%
- 用 C++20/23 + constexpr + CRTP 的驱动层 → 经常比 C 小 5–15%(因为常量折叠 + 更好内联)
总结一句话(可以直接怼给质疑的人)
“C++ 不会必然导致代码膨胀——不加约束地乱用才会。现代嵌入式 C++ 的正确姿势是:零成本抽象 + 有意识地禁用昂贵特性 + 工具链强力死代码消除 = 通常和 C 差不多大小,甚至更小,但可维护性、安全性、开发效率大幅提升。”
你现在项目里用 C++ 到什么程度了?
- 还在纯 C?
- 已经用类/模板但关了异常?
- 开始尝试 C++20/23 新特性?
或者你最担心哪部分会膨胀(模板?虚函数?RAII?),我们可以针对性测一测或给更具体的写法对比。