我第一次写程序的时候 屏幕上跑通了一个"Hello World" 但我盯着那行字想的是 这些英文字母到底是怎么变成屏幕上跳动的像素的
答案在编译器里
编译器不是单个程序 是一条流水线 你写的一行源代码在编译器眼里不是文字 是待加工的零件 它要经过五个车间 每一站都拆得更碎 最后拼成一个完全不同的东西——机器能直接读懂的二进制指令
第一站:词法分析
编译器要做的第一件事 是把你的代码拆成最小的组成单元 叫token(记号) 比如 int a = 3 + 5;这一行 编译器会拆成 int | a | = | 3 | + | 5 | ; 七个独立的token
看起来像切词练习 但难点在于每种语言的边界规则不一样 空格 分号 括号 每种切割方式不同 编译器还得知道引号里面的空格不能切
第二站:语法分析
拆完token之后 编译器要按照语言的语法规则把它们组装成一棵树 这棵树叫抽象语法树 AST
你可以想象成给一个句子画结构图 主谓宾是树干 定语状语是树枝 在编程语言里 3 + 5 变成一棵小树 根节点是"+" 左叶子是3 右叶子是5 而 a = 3 + 5 的树更大 根节点是"=" 左边是a 右边是刚才那棵加法树
到了这一步 编译器第一次真正理解了你的代码——不再是一串扁平的记号 而是一棵立体的树
第三站:语义分析
树搭好了 编译器要检查它合不合理 比如你声明了一个int变量 却给它赋值一个字符串 编译器会在这里拦住你 又比如你调用了一个不存在的函数 它也会在这里告诉你 不行
这一站不做复杂的逻辑推理 只做"常识判断" 每种操作在语言里有没有定义 类型配不配 在AST上走一遍就能看出来
第四站:中间代码生成与优化
到这里 编译器把AST翻译成一种中间语言 不是人类语言 也不是最终的机器语言 介于两者之间
然后优化器开始动手 比如你连续写了 a = 1;a = 2; 优化器会发现第一个赋值是浪费的 直接扔掉 只保留 a = 2 又比如一个循环里反复计算同一个不变的表达式 优化器会把结果存起来 循环里直接取 不用每次重新算
这个阶段的优化不改变程序的含义 只改变它的表达方式 让它更快 更省
第五站:目标代码生成
最后一步 编译器把优化过的中间代码翻译成CPU的指令 也就是真正的二进制 0和1
不同架构的CPU说不同的语言 x86的指令和ARM不一样 同样一行C代码 编译器得知道你装在什么芯片上 才能生成正确的二进制
至此 编译走完了全程 你写的文字 变成了CPU认识的电压信号
写这篇文章的时候我一直在想一个事 编译器做了这么多复杂的工作 但它最厉害的地方不是"聪明" 而是每一步都严格遵循规则 不猜你在想什么 不关心你好不好 不管你的代码有什么故事 它就是冷冰冰地把一种结构按照数学规则变成另一种结构
某种意义上 编译器比大多数人类都诚实 因为它的规则是公开的 每一步都可以被验证 你怎么写 它就怎么译 不多一字 不少一位