STM32 - Embedded IDE - GCC - 编译器设置的最佳方案
导言
有些同学刚从Keil或者IAR等开发环境切换到VScode + GCC开发环境时,不知道构建器怎样设置最合适。所谓最合适就是代码体积更小,降低堆栈的要求,提高代码的执行效率等等。

最佳方案


一、全局选项

1.1、–specs=nano.specs
作用:
使用 newlib-nano 库,这是标准 C 库(newlib)的精简版本
优点:
- 大幅减少代码体积: 可节省 20-60 KB Flash
- 减少 RAM 使用: 降低堆栈需求
- 专为资源受限的嵌入式系统优化
功能差异:

1.2、–specs=nosys.specs
作用:
提供系统调用(syscalls)的 空实现(stub)

如上所示,STM32CubeIDE生成了syscalls.c的标准实现。所以,不要添加--specs=nosys.specs,因为已经有自己的syscalls.c。
二、C/C++编译器

2.1、链接时优化(-flto)
作用:
= 在链接阶段进行全局代码优化,而不仅仅是在编译单个文件时优化。
工作原理:
传统编译流程:
file1.c → [优化] → file1.o ──┐
file2.c → [优化] → file2.o ──┼→ [链接] → firmware.elf
file3.c → [优化] → file3.o ──┘↑只能看到单个文件内部
启用 LTO 后:
file1.c → file1.o ──┐
file2.c → file2.o ──┼→ [链接 + 全局优化] → firmware.elf
file3.c → file3.o ──┘ ↑可以看到所有文件,进行跨文件优化
何时使用 LTO?
推荐使用:
- Release 版本 (最终产品固件)
- Flash 空间紧张 (接近容量上限)
- 性能要求高 (需要榨取最后的性能)
- 稳定的代码 (不频繁修改)
不推荐使用: - Debug 版本 (日常开发调试)
- 频繁编译 (开发阶段)
- 需要精确调试 (查找 bug)
- 编译速度优先 (快速迭代)
2.2、Function Sections(-ffunction-sections)
作用:
- 将每个函数放入独立的代码段(section)中。
默认行为:
不使用时:
.text 段:├─ function1()├─ function2() <-- 所有函数在一起└─ function3()
启用后:
使用 -ffunction-sections:
.text.function1
.text.function2
.text.function3 <-- 每个函数独立段
2.3、Data Sections(-fdata-sections)
作用:
- 将每个全局变量/静态变量放入独立的数据段中
默认行为:
不使用时:
.data 段:├─ global_var1├─ global_var2 <-- 所有变量在一起└─ global_var3
启用后:
使用 -fdata-sections:
.data.global_var1
.data.global_var2 <-- 每个变量独立段
.data.global_var3
2.4、Signed Char(-fsigned-char)
作用:
- 强制将 char 类型解释为有符号字符(signed char)。
默认行为: - 在某些架构(如ARM)上,char 默认是无符号的(unsigned char),范围是 0-255。
使用场景: - 当你需要 char 的范围是 -128 到 127 时启用此选项。
影响: - 确保跨平台代码的一致性,特别是在处理字符串和字节数据时。
不建议勾选,按照让架构来决定。
2.5、禁用运行时类型信息(-fno-rtti)
作用:
- 禁用C++运行时类型识别。
优点: - 减少代码体积(通常可节省 5-10% 的 Flash 空间)。
- 减少 RAM 使用。
- 提高执行效率。
缺点: - 无法使用 dynamic_cast 和 typeid 等特性。
嵌入式MCU推荐:
- 强烈推荐启用,因为嵌入式MCU系统很少需要 RTTI。
有C++代码才有影响
2.6、禁用异常处理(-fno-exceptions)
作用:
禁用C++异常处理机制。
优点:
- 显著减少代码体积(可节省 10-20% 的 Flash)。
- 减少栈使用。
- 提高代码执行的可预测性。
缺点: - 无法使用 try/catch/throw 语句。
**嵌入式MCU推荐: ** - 强烈推荐启用,异常处理在嵌入式系统中开销太大且不确定。
有C++代码才有影响
2.7、Not use __cxa_atexit()(-fno-use-cxa-atexit)
作用:
- 不使用
__cxa_atexit函数来注册析构函数。
背景: __cxa_atexit是C++标准库用于注册全局对象析构函数的机制。
优点:- 减少代码体积。
- 避免依赖完整的C++运行库。
嵌入式推荐: - 推荐启用,因为嵌入式程序通常不会"退出",全局对象析构很少被调用。
有C++代码才有影
2.8、禁用本地静态变量的线程安全初始化(-fno-threadsafe-statics)
作用:
- 禁用局部静态变量的线程安全初始化
背景: - C++11 要求局部静态变量在多线程环境下安全初始化,编译器会生成额外的互斥锁代码
优点: - 减少代码体积
- 避免隐式的互斥锁开销
- 提高执行效率
缺点: - 如果多个线程同时访问未初始化的局部静态变量,可能导致竞态条件
嵌入式推荐: - 视情况而定。
- 如果是单线程或简单RTOS: 推荐启用
- 如果有复杂多线程: 谨慎使用
裸机勾选,RTOS的话不勾选
