有没有遇到过这两种场景?
- 编译出来的固件太大,烧录时提示空间不足;
- 用调试器单步,发现某个变量“突然不见了”,或者循环被优化得对不上源码。
十有八九,是 GCC 的 -O 优化等级没选对。
一、-O 优化等级
-O(大写字母 O,不是数字 0)是 GCC 用来控制优化强度的开关。
它决定了编译器在“编译速度 / 执行速度 / 代码体积 / 内存占用”之间怎么取舍。等级越高,编译器做的事越多,生成的机器码通常更小更快,但编译更慢,调试也更难对应源码。
# 小写 -o:指定输出文件名(注意和 -O 区分!)
gcc myfile.c -o myfile
# 大写 -O:指定优化等级
gcc -O2 myfile.c -o myfile
术语对照:optimization level(优化等级)、code size(代码体积)、execution time(执行时间)、compile time(编译时间)。
二、优化等级
GCC 主流优化分为 5 档,外加一个 Ofast。
-O0:关闭一切优化(默认)
这是 GCC 默认等级。目标只有一个——让编译最快完成,同时让调试所见即所得。
- 变量不会被“优化掉”,断点、单步、看变量值都最准;
- 代价是代码最大、跑得最慢。
- 很多 IDE 新建工程默认就是
-O0。
提示:调试阶段用 -O0 没问题,但正式发布如果还留着 -O0,固件体积会比较大。
-O1:最基础的优化
在不显著增加编译时间的前提下,做最基础的体积和执行速度优化。
单独写 -O(不带数字)等价于 -O1。适合“稍微优化一下,但不想等太久”的过渡场景。
-O2:日常发布的推荐
在 -O1 基础上启用更多优化选项,用更长的编译时间换取更优的代码。
- 不极端、不冒险,稳定性好;
- 是绝大多数工程 release 版本的默认选择。
发布版先上 -O2,基本不会错。
-O3:最高强度优化(慎用)
在 -O2 的全部优化之上,再加更激进的手段,比如循环展开(loop unrolling)、激进的内联等。
⚠️ 重点坑:-O3 不一定让代码变小,反而可能为了提速把代码撑大。而且某些激进优化会让调试信息对不上源码,排查问题更痛苦。
经验法则:除非程序是纯计算密集、且确认 -O3 收益明显,否则别用 O3。
-Os:为体积而生(嵌入式首选)
在 -O2 的基础上,关掉那些会让目标文件变大的优化。
- 专为 Flash/磁盘空间紧张、或 CPU 缓存较小的机器设计;
- 嵌入式、单片机、Bootloader 场景的常用选择。
🔹 -Ofast:O3 + 不要精度的快
在 -O3 基础上再开启 -ffast-math,允许编译器牺牲浮点精度换取更快的数学运算。
⚠️ 金融、控制、传感等对数值敏感的场景,禁止使用。
三、表格说明
| 选项 |
优化目标 |
执行时间 |
代码大小 |
内存占用 |
编译时间 |
-O0 |
优化编译速度(默认) |
+ |
+ |
- |
- |
-O1 / -O |
兼顾体积和执行 |
- |
- |
+ |
+ |
-O2 |
更多优化(推荐) |
-- |
|
+ |
++ |
-O3 |
最高优化(慎用) |
--- |
|
+ |
+++ |
-Os |
优化代码体积 |
-- |
-- |
++ |
++ |
-Ofast |
O3 + 牺牲精度 |
--- |
|
+ |
+++ |

符号含义:+ 增加、- 减少,符号越多幅度越大。
四、实战:换优化,省出 16KB
以测试工程为例,做一个最直接的对比:
- 默认
-O0:系统裁剪后固件 56.93 KB
- 切换到
-O2:固件缩小到 40.14 KB
一次改动,体积减少约 29%。 在 Flash 只有几百 KB 的单片机上,这 16KB 可能就是“能不能塞下新功能”的差别。
// 例:一段会被优化掉"冗余"的循环
for (int i = 0; i < 100; i++) {
buf[i] = 0; // -O2 可能被识别为 memset 语义并内联展开
}
// 优化后机器码更紧凑、执行更快,但调试时单步行为会和源码不完全一致
建议:开发期用 -O0 保证可调试,出 release 时切 -O2;Flash 吃紧就上 -Os。
五、选择建议
- 调试中 →
-O0(所见即所得)
- 普通发布 →
-O2(稳,不会错)
- Flash 紧张 →
-Os(嵌入式首选)
- 纯算力且验证过 → 才考虑
-O3
- 数值敏感 → 远离
-Ofast
优化等级不是越高越好,适合场景的才是最好的。
参考链接