一、前言
在我们开发编程时,随着对一个项目进行的功能扩展,我们编写的程序占用大小会越来越大,这个时候会出现程序体积过大引发的问题,如下举例,这篇文章将记录我总结的优化的一些方法。
- Flash 空间吃紧(无法烧录)
- 编译出错(代码超出 ROM)
- RAM 使用不当时引发堆栈冲突(数组值混乱,申请内存不成功等等)
- 更新困难,功能扩展受限
二、优化程序代码
这个时候可以优化程序代码来减少程序内存的占用,这里强烈推荐、重复代码封装函数和静态替代 malloc,具体优化方法如下:
-
封装重复逻辑为函数
-
使用
const常量数组代替硬编码数据,示例
usart_send(0x31);
usart_send(0x32);
usart_send(0x33);
usart_send(0x34);
usart_send(0x35);
//改写为
const uint8_t msg[] = "12345";
for (int i = 0; i < sizeof(msg)-1; i++) {
usart_send(msg[i]);
}
- 用宏/函数统一初始化逻辑
//例如GPIO
#define INIT_GPIO_OUTPUT(port, pin) \
gpio_mode_set(port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, pin); \
gpio_output_options_set(port, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, pin)
- 减少
float和double的使用(特别是 printf)
//使用 int 或 fixed-point 运算替代浮点运算
//避免在 printf() 中使用 %f,会引入 大量浮点格式支持代码
printf("value = %f\n", my_float);
//改写为
printf("value = %d.%03d\n", (int)val, (int)((val - (int)val) * 1000));
-
删除未用函数、变量、printf 日志等调试代码(这个自己看着来)
-
合理使用
static inline(避免小函数调用开销),让编译器展开代码(权衡空间与效率)
三、Keil 5编译器优化选项设置
在代码上没有什么可以优化的地方就可以使用编译器的优化功能,设置不同的优化等级可以节省不同大小的内存,各个等级有自己的特点,针对现阶段选择就行。
1、编译器优化选项设置在哪里选择?打开设置然后选择C/C++如图:
2、编译等级分类:我的编译器版本是6,如上图,编译优化选项有默认、O0、O1、O2、O3、Ofast、Os balanced、Oz image size、Omax
3、编译优化等级说明:
| 等级 | 含义 | 特点 |
|---|---|---|
| 默认(Default) | 通常相当于 O0 或 O1,取决于项目设置 | 稳定但没有特别优化性能或大小 |
| O0 | 无优化(No optimization) | 编译速度最快、调试体验最好。代码最慢、体积最大 |
| O1 | 轻优化(Some optimization) | 适度优化性能,保持较好调试能力 |
| O2 | 中等优化(More optimization) | 平衡性能和代码大小,是常用等级 |
| O3 | 高优化(Full optimization) | 最大性能优化,可能会打乱函数结构,不适合调试 |
| Ofast | 极致性能优化(Aggressive) | 启用所有 O3 优化并加入违反 IEEE 或标准行为的优化(如去除 NaN 检查等) |
| Os balanced | 平衡优化和代码体积(Size and Speed) | 介于 O2 与 Oz 之间,适用于资源受限但又有性能要求的系统 |
| Oz image size | 最小体积优化 | 最大限度压缩代码体积,适合 ROM 空间紧张的嵌入式系统 |
| OMax | 厂商预定义最大优化级别(Keil 特有) | 实际等价于 Ofast 或 O3+Os,根据目标 CPU 平台特化优化策略 |
四、优化等级实测
1、怎么看程序大小?如下,圈出的一行:
含义如下:
| 字段名 | 全称 | 含义 | 存储区域 | 是否占Flash? | 是否占RAM? |
|---|---|---|---|---|---|
Code | Code Size | 代码段:函数、指令等编译后的机器码 | Flash | ✅ 是 | ❌ 否 |
RO-data | Read-Only Data | 只读数据段:如 const 常量、字符串常量等 | Flash | ✅ 是 | ❌ 否 |
RW-data | Read-Write Data | 已初始化全局/静态变量(如 int a = 5;) | Flash(初始化值) & RAM(实际运行) | ✅ 是 | ✅ 是 |
ZI-data | Zero-Initialized Data | 未初始化全局/静态变量(如 int b;) | RAM(运行时置 0) | ❌ 否 | ✅ 是 |
Flash 总占用 ≈ Code + RO-data + RW-data
RAM 总占用 ≈ RW-data + ZI-data
2、再多说明不如来一次实际测试,数据如下(Os和Oz很容易跑飞,谨慎选择):
| 等级 | 程序大小 |
|---|---|
| 默认(Default) | Program Size: Code=39036 RO-data=3860 RW-data=76 ZI-data=2580 |
| O0 | Program Size: Code=39036 RO-data=3860 RW-data=76 ZI-data=2580 |
| O1 | Program Size: Code=33556 RO-data=2004 RW-data=76 ZI-data=2580 |
| O2 | Program Size: Code=38484 RO-data=2080 RW-data=76 ZI-data=2580 |
| O3 | Program Size: Code=41140 RO-data=2128 RW-data=76 ZI-data=2580 |
| Ofast | Program Size: Code=41052 RO-data=2128 RW-data=76 ZI-data=2580 |
| Os balanced | Program Size: Code=31124 RO-data=2004 RW-data=76 ZI-data=2580 |
| Oz image size | Program Size: Code=30372 RO-data=1996 RW-data=76 ZI-data=2580 |
| OMax | Program Size: Code=51140 RO-data=2496 RW-data=20 ZI-data=2580 |