能用,但必须禁用异常和rtti、选用arm compiler 6并配置c++17;全局对象构造不可调用hal函数;头文件需加extern "c";禁止new/delete,仅用栈或静态分配。

能用,但必须关掉 C++ 的“豪华功能”,否则代码跑不起来、Flash 烧不满、RAM 直接爆掉。
编译器必须切到 ARM Compiler 6,并禁用异常和 RTTI
Keil MDK 默认用 ARM Compiler 5(ARMCC),它对 C++ 支持有限,尤其不支持现代 C++ 初始化语法、模板推导等。换成 ARMCLANG(即 ARM Compiler 6)后,才能用 class、constexpr、auto 等特性,且生成代码体积可控。
- 在 Keil → Project → Options for Target → Target → ARM Compiler 选
ARM Compiler 6.18(或最新稳定版) - 在 C/C++ → Misc Controls 中添加:
--cpp17 -fno-exceptions -fno-rtti - 不加
-fno-exceptions会导致链接时报__cxa_begin_catch未定义;不加-fno-rtti会多出typeinfo段,浪费几百字节 Flash
全局对象构造函数不能依赖 HAL 初始化完成
C++ 全局对象(比如 Led led(GPIOA, 5);)的构造函数会在 main() 之前执行,但此时 HAL_Init() 和 SystemClock_Config() 还没跑,GPIO 时钟没开、寄存器地址可能还没映射好——结果就是构造函数里调 __HAL_RCC_GPIOA_CLK_ENABLE() 直接挂掉。
- 所有外设操作(如使能时钟、配置引脚、初始化
HAL句柄)必须放在main()里,或封装进显式初始化函数(如led.init()) - 构造函数里只做纯数据成员赋值,禁止调用任何 HAL 函数或寄存器写操作
- 如果非要自动初始化,得自己写一个轻量级启动文件,把
__libc_init_array调用时机挪到SystemInit()之后(极少见,不推荐初学者碰)
头文件混用 C 和 C++ 时必须加 extern "C"
HAL 库、CMSIS 头文件、你从厂商 SDK 拿来的驱动(比如 GO_M8010_6.h)全是 C 风格写的。C++ 编译器默认会对函数名做 name mangling(比如 HAL_GPIO_WritePin 变成 _Z18HAL_GPIO_WritePinP12GPIO_TypeDefj15GPIO_PinState),导致链接时报 Undefined symbol。
- 在
.hpp文件里包含 C 头文件前,必须包一层:#ifdef __cplusplus extern "C" { #endif #include "stm32f4xx_hal.h" #include "GO_M8010_6.h" #ifdef __cplusplus } #endif - 不能只包你用到的几个函数声明,必须整个头文件都包住,否则宏定义(比如
__HAL_RCC_GPIOA_CLK_ENABLE)里的内联汇编或寄存器访问也会出错 - 如果你自己写的 C 函数要被 C++ 调用,也得在对应
.h里加同样结构的extern "C"
别用 new/delete,栈和静态分配是唯一安全选项
STM32 常见型号(F4/F7/G4)RAM 一般只有 128KB~512KB,且没有 MMU 和虚拟内存。一旦开了 malloc,碎片化 + 重入问题会让系统某天突然卡死,而且调试极其困难。
- 所有对象实例必须声明为全局静态或栈上变量:
Led led(GPIOA, 5);或static Led led(GPIOA, 5); - 类内部缓冲区(如 UART 接收 FIFO)必须用固定大小数组:
uint8_t rx_buffer[64];,而不是std::vector<uint8_t></uint8_t>或new uint8_t[64] - 如果真需要运行时动态尺寸,改用环形缓冲区(
struct ringbuf)+ 静态池管理,而不是依赖堆
最常被忽略的一点:C++ 类封装看起来很干净,但每个虚函数表(哪怕只用了一个 virtual)、每个 std::string 或 std::map 都会悄悄吃掉几百字节 Flash 和 RAM。在 STM32 上,不是“能不能用”,而是“敢不敢为这点便利多烧 2KB 代码”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











