优化选项:
1. -O0(默认级别)
- 目标:无优化。编译器以最快速度完成编译,保留源代码的原始逻辑结构。
- 效果:生成的代码最易调试,所有变量在内存中保持可见,断点行为与源码高度一致。但运行速度最慢,代码体积较大(未消除冗余)。
- 适用场景:开发调试阶段,需要单步跟踪或检查变量值时使用。
2. -O1(基本优化)
- 目标:在合理编译时间内进行常见优化,不显著增加代码体积。
- 效果:开启
-fauto-inc-dec、-fcompare-elim、-fcprop-registers、-fdce、-fdefer-pop等约 30~40 个独立优化项。会做简单的常量折叠、死代码删除、寄存器分配等,但不包含循环变换或内联函数。 - 适用场景:需要一定性能提升,但仍希望保留较好可调试性的情况。编译时间比
-O0稍长。
3. -O2(推荐优化)
- 目标:在不明显增加代码体积的前提下,大幅提升运行速度。这是 GCC 官方推荐的“生产环境”优化级别。
- 效果:在
-O1基础上额外开启约 100 多个优化项,包括指令调度、循环展开、函数内联(有限制)、全局公共子表达式消除、自动向量化(部分架构)等。不会启用空间换时间的激进优化(如完全循环展开、激进内联)。 - 适用场景:绝大多数软件发布版本的首选,平衡了性能、代码体积和编译时间。
4. -Os(优化代码体积)
- 目标:在尽量不影响运行速度的前提下,最小化生成的可执行文件大小。
- 效果:基于
-O2的优化集合,但关闭那些会增加代码体积的优化(例如某些循环展开、函数内联阈值降低),同时额外启用一些专门缩小代码的技巧(如合并重复代码、压缩跳转指令)。有时会比-O2更慢,但体积通常能减少 10%~30%。 - 适用场景:嵌入式系统、存储受限的设备
常用优化选项意义:
1. -fauto-inc-dec
- 含义:自动增量/减量优化(Auto-increment/decrement)。
- 作用:识别对内存地址寄存器的顺序访问模式,将其转换为带有后自增或前自减的寻址方式(例如
*ptr++对应的汇编指令)。这利用了目标 CPU 提供的“自动增减”寻址模式(如 ARM 的LDR R0, [R1], #4),可减少显式的加减指令,从而降低代码大小和执行时间。 - 典型效果:循环遍历数组时,省去单独的地址更新指令。
2. -fcompare-elim
- 含义:消除比较指令(Compare elimination)。
- 作用:在目标架构中,许多算术运算(如加法、减法)会自动设置条件码寄存器(如 Z、N、C 标志位)。该优化会分析后续的条件分支是否可以直接复用之前算术运算产生的条件码,从而删除显式的
cmp/test指令。 - 典型效果:减少比较指令的数量,尤其适用于条件判断密集的代码。
3. -fcprop-registers
- 含义:寄存器复制传播(Copy propagation on registers)。
- 作用:将从一个寄存器复制到另一个寄存器的值,直接传播到后续的使用点,从而消除多余的
mov指令。例如r1 = r0; ... use(r1)会被替换为... use(r0),并删除mov。 - 典型效果:减少寄存器间的数据搬运,降低指令数和寄存器压力。
4. -fdce
- 含义:死代码消除(Dead Code Elimination)。
- 作用:删除那些计算结果从未被使用的代码,以及永远不可达的代码段。例如:赋值后该变量不再被读取、if(0) 内的分支等。
- 典型效果:直接缩小二进制体积,提升执行效率。
5. -fdefer-pop
- 含义:延迟函数参数出栈(Defer popping of function arguments)。
- 作用:在调用约定要求调用者清理参数的平台上(如 cdecl),通常每次函数调用后都会立即调整栈指针(
add sp, #n)。此优化会将多个相邻调用的栈调整合并为一次,推迟到最后统一恢复。 - 典型效果:减少栈指针调整指令的次数,略微减小代码体积并提高速度。
常见情况
目标程序大小-Os > -O0
这是一个常见的反直觉现象,原因如下:
1. 函数内联(Inline)导致代码膨胀
-Os会适度启用函数内联(即使没有inline关键字),尤其是小函数。如果一个函数被多处调用,内联会在每个调用点复制一份函数体。若该函数体相对较大,且调用次数多,总代码量可能超过原本调用+函数体的总和。-O0从不内联,所有调用保留为call指令,函数体只存一份,体积往往更紧凑。
2. 循环展开(Loop Unrolling)的副作用
- 虽然
-Os默认禁用循环展开(-fno-unroll-loops),但某些优化组合(如-fgcse后的循环优化)仍可能产生部分展开或复制,导致代码量增加。
3. 公共子表达式消除(CSE)中的代码复制
-Os会尝试通过“计算一次、多处复用”来减小体积,但在某些情况下,为了消除分支或跳转,编译器可能选择复制一段代码到不同路径(即“代码外提”的反向操作),反而增加了总字节数。
4. 对齐填充与跳转表
-Os为了性能可能会强制函数或循环入口对齐(如.align 4),在紧凑的嵌入式环境中,额外的 NOP 填充可能超出预期。- 对于 switch-case,
-O0常使用简单的 if-else 链(体积小但慢),而-Os可能生成跳转表(Jump Table),虽然执行更快,但表本身占用额外空间。
5. 寄存器分配策略差异
-O0倾向于频繁访存取数(大量 load/store),指令虽多但每条较短;-Os会尽量用寄存器暂存,但为了节省寄存器可能采用更复杂的寻址模式或多条短指令组合,最终总字节数可能更高。
6. 调试信息的影响(次要)
-O0默认带-g(调试符号),但.text段(代码段)大小不受影响;若比较的是整个可执行文件(含 debug 段),-O0会更大。但通常讨论的是代码段大小,因此这不是主因。
798

被折叠的 条评论
为什么被折叠?



