单片机C++开发必须禁用虚函数、异常处理(-fno-exceptions)和RTTI(-fno-rtti),禁用new/delete,优先使用静态/栈分配与模板策略模式,严控RAM与Flash开销。

有,而且限制非常具体——不是“能不能用”,而是“哪些特性必须禁用、哪些写法会悄悄吃掉你仅有的几KB RAM”。
虚函数和多态在单片机上直接增加RAM开销
每个含虚函数的类都会生成一个虚函数表(vtable),通常占 4–8 字节;若类实例化多个对象,vtable 是共享的,但每个对象还要额外存一个指向 vtable 的指针(通常是 4 字节)。在 RAM 仅 4KB 的 STM32F0 或 AVR 上,10 个虚基类可能就吃掉上百字节。
- 现象:启用虚函数后,
sizeof类实例突然变大,map文件里出现_ZTV开头的符号段 - 替代方案:用模板 + 策略模式实现编译期多态,或纯函数指针表(手动管理,无运行时开销)
- 检查方法:编译后用
arm-none-eabi-size -A your.elf对比含/不含虚函数的 .bss/.data 段变化
-fno-exceptions 和 -fno-rtti 不是可选项,是必选项
异常处理机制会让 GCC 插入大量支撑代码(__cxa_begin_catch 等),实测在 STM32F103 上使 Flash 增加 20–30%;RTTI 则让每个类型信息都保留在只读段中,浪费 Flash 且无法被 --gc-sections 清理。
- 错误做法:保留默认 C++ 编译选项,只在
main()里不 throw - 正确做法:在 Makefile 或 IDE 工具链设置中强制添加
-fno-exceptions -fno-rtti - 后果:若漏掉
-fno-rtti,dynamic_cast或typeid会静默链接失败,或在运行时报错undefined reference to __gxx_personality_v0
new/delete 在绝大多数单片机项目中应彻底禁用
堆内存碎片化不可预测,且多数裸机环境没实现完整的 malloc 后端。更危险的是:new 失败时不抛异常(因已禁用),而是返回 nullptr —— 如果你没检查,后续解引用直接跑飞。
立即学习“C++免费学习笔记(深入)”;
- 安全替代:全局对象、栈对象、预分配静态池(如
static Led leds[8];) - 陷阱:即使写了
if (p) { ... },new的调用本身也引入了 libc 的 heap 管理代码,增大 Flash - 验证方式:在
linker script中把.heap段大小设为 0,再编译 —— 若报错region 'RAM' overflowed,说明有隐式动态分配
类构造函数能用,但得清楚它怎么执行
C++ 全局对象的构造函数会在 main() 前自动调用,这很方便,但依赖启动文件中的 .init_array 段和 __libc_init_array()。很多精简版 startup.s 会删掉这部分,导致构造函数完全不执行。
- 现象:LED 没亮,但
main()里手动调用led.init()就正常 - 确认方法:查看 map 文件中是否有
.init_array段,以及__libc_init_array是否被链接进来 - 稳妥做法:显式声明
__attribute__((constructor))函数,或直接在main()开头调用初始化逻辑
真正卡住人的从来不是“C++ 能不能跑”,而是某次升级 SDK 后,第三方库悄悄启用了 RTTI,或者某个模板头文件在没注意时实例化了三份 std::vector —— 这些细节不会报错,只会让你的固件在量产前一周突然超 RAM。盯紧 map 文件,比写一百行类更重要。


















