15 编译器偷偷对你的代码做了什么

编译器偷偷对你的代码做了什么?

一个可疑的优化

int x = 10;
int y = 20;
if (0) {
    return x + y;  // 这行会被编译吗?
}
return x;

把这段代码编译,你猜最终生成的机器码里有没有 x + y 的指令?

没有。 编译器的优化阶段会直接把它"扔掉"。

你以为的

编译器就是逐行翻译,把 C 代码机械地转成机器码。

实际情况:编译器优化

GCC 有多个优化级别(-O0-O3-Os),每个级别启用不同的优化策略。

级别说明编译速度代码质量
-O0无优化最快最差
-O1基本优化较好
-O2大部分优化(推荐)较慢
-O3激进的优化最好但代码体积大
-Os优化体积兼顾大小和速度

常见优化手段

1. 常量折叠(Constant Folding):

int x = 3 + 4 * 5;   // 编译时直接算成 23

2. 死代码消除(Dead Code Elimination):

if (0) { ... }  // 永远不会执行,直接删除

3. 常量传播(Constant Propagation):

int x = 42;
int y = x + 1;  // 变成 int y = 43;

4. 内联展开(Inline Expansion):

int square(int x) { return x * x; }
int a = square(5);  // 变成 int a = 5 * 5 = 25;

5. 公共子表达式消除(CSE):

int a = x * y * 2;
int b = x * y * 3;  // x * y 只需算一次

动手验证

保存为 optimize.c

#include <stdio.h>

int sum(int n) {
    int total = 0;
    for (int i = 0; i < n; i++) {
        total += i;
    }
    return total;
}

int dead_code() {
    int a = 10;
    int b = 20;
    if (0) {
        return a + b;      // 死代码,优化后会消失
    }
    return a;
}

int main() {
    int result = 0;
    
    // 循环展开的候选
    for (int i = 0; i < 10; i++) {
        result += sum(100);
    }
    
    // 常量传播的候选
    int x = 10;
    result += dead_code();
    
    printf("result = %d\n", result);
    return 0;
}

编译并对比汇编:

$ gcc -O0 -o optimize_O0 optimize.c
$ gcc -O2 -o optimize_O2 optimize.c
$ ./optimize_O0 && ./optimize_O2

命令解释:同一个源文件分别用 -O0(无优化)和 -O2(开启优化)编译。两个可执行文件输出结果应该相同,但内部实现完全不同。

运行结果(输出一致):

result = 49510
x = 10

用汇编对比:

$ gcc -O0 -S -o /tmp/O0.s optimize.c
$ gcc -O2 -S -o /tmp/O2.s optimize.c
$ wc -l /tmp/O0.s /tmp/O2.s

命令解释:-S 让 GCC 只编译到汇编就停止(不生成可执行文件)。wc -l 统计行数。

运行结果:

 155 /tmp/O0.s
 122 /tmp/O2.s

-O2 的汇编比 -O0 少了 33 行(约 21%)!

逐行看汇编差异

-O0 下的 sum 函数:

$ grep -A 30 '<sum>:' /tmp/O0.s

会看到真实的循环结构(有 cmp / jge / add 等指令)。

对比 -O2

$ grep -A 10 '<sum>:' /tmp/O2.s

可能发现循环被展开甚至被完全消除(如果计算结果可以在编译时确定)。

更极端的例子

volatile int keep = 1;  // volatile 阻止优化

int dead_code() {
    int a = 10, b = 20;
    if (0) return a + b;  // 死代码
    return a;             // 优化后只有这一句
}
  • -O0 下:dead_code 有完整的函数体(即使 if(0) 内的代码依然存在)
  • -O2 下:dead_code 被优化成 return 10if(0) 完全消失

用图示理解编译器工作流程

源代码 (.c)
    ↓
预处理 (cpp)  →  gcc -E  展开宏、处理 #include
    ↓
编译 (cc1)    →  gcc -S  生成汇编
    ↓
汇编 (as)     →  gcc -c  生成目标文件 (.o)
    ↓
链接 (ld)     →  gcc     生成可执行文件
    ↓
优化贯穿以上全程

核心启示

  1. 编译器比你想象的聪明——你的代码可能被大幅改写,只要语义相同
  2. 不要在"不可能"的分支里放关键逻辑——编译器可能直接删除
  3. volatile 可以阻止优化——告诉编译器"这个变量可能被意想不到地修改"
  4. -O0 适合调试,-O2 适合发布——调试时用 -O0,发布用 -O2

下期预告

const int c = 42; 真的不能改吗?volatile int flag = 1; 到底阻止了什么优化?

下一期《const 和 volatile 在编译器眼里是什么?》,看编译器怎么对待这两个关键字,以及为什么硬件寄存器需要 volatile const。


更多内容详见专栏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顾鉴行思

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值