编程语言一般分为编译期、运行期
有的编程语言是编译期与运行期合并在一起的,比如Python、PHP…即解释型语言
有的则是编译期与运行期分开的,比如C、C++、Java…即编译型语言
今天咱们深入聊聊编译期的C语言
运行期的C语言,我在我之前的课程中已经详细讲过,感兴趣的自己去看视频
编译期,即编译器工作的期间
一般编译器,都是基于《编译原理》实现的
但是C语言的编译器的实现,在《编译原理》基础上做了拓展,如图
接下来详细讲讲C语言编译器的各个阶段
预处理
在真正编译C语言程序之前,需要对C语言程序进行预处理,预处理是由集成在编译器中的预处理器完成的
从《编译原理》的角度,词法分析是编译器的第一个阶段。但是从C语言编译器的角度,预处理是第一个阶段
预处理阶段主要完成四件事:删除注释、宏展开、文件包含、条件编译
如果你要做实验论证,gcc -E即可实现,比如
举个例子吧:删除注释、宏展开,比如代码
预处理以后,注释没有了,宏展开了。C语言的预处理器,就是简单的宏替换
你会发现,预处理器会在开头多生成点东西,这些东西是什么呢?有什么用呢?
这些看似神秘的 # 开头的行,实际上叫做行控制信息(line control directives),是 GCC 预处理器在输出中自动插入的特殊指令,是方便后续编译器、调试器、诊断工具理解源码位置的信息
词法分析
经过预处理后,得到的就是完整的C语言程序了,就可以开始编译了。词法分析器启动…
词法分析器的职责是:输入源程序,输出token
来看看上面的程序生成的token
如何查看C语言程序生成的token呢?
你可能想问:为什么用clang,而不是用gcc?因为gcc作为老牌的编译器,不支持这个功能
来看看词法分析器生成token的过程
如果你想透彻理解词法分析的底层原理,你可以使用词法分析工具flex生成词法分析器,去实战。或者自己从零写一个词法分析器,体会将程序的点点滴滴转成token的过程,你的困惑就解开了…(这部分内容,我做的课程手写编程语言中有教,感兴趣的可以咨询班主任jvm-anan)
语法分析
拿到源程序对应的token,就可以去做语法分析了。语法分析器启动…
语法分析器的职责是:输入token,输出抽象语法树AST。后面的阶段,都是围绕AST进行的
换个程序,来看看经过语法分析生成的抽象语法树
对应的AST
如何查看呢?两种方式
其实gcc也可以,就是生成的文件太多,看起来不直观
会生成这些文件
来看看token生成AST的过程,比如print语句
如果你想透彻理解语法分析的底层原理,你可以使用语法分析工具bison生成语法分析器,去实战。或者自己从零写一个语法分析器,体会将token转成AST的过程,你的困惑就解开了…(这部分内容,我做的课程手写编程语言中有教,感兴趣的可以咨询班主任jvm-anan)
语义分析
语义分析器的职责是:输入AST,输出AST + 符号表
比如程序生成的AST长这样
语义分析器会遍历AST,生成符号表。这个无法查看,你可以大概理解成是这样
然后是在AST表中加上注解
语义分析是一种什么感觉呢?就像拿着一棵语法树边走边做笔记,遇到变量声明就记下来(符号表),遇到变量使用就去查阅笔记,发现有问题就报错
语义分析具体做哪些事情呢?我们所知的如:类型检查、类型转换、生命周期检查、控制流检查、访问权限…完整的如图
语义分析是编译器前端中逻辑最复杂、实现难度最高、语言标准依赖最强、对整体编译正确性最关键的阶段。我在我的课程手写编程语言中,做了初步的语义分析,我觉得让大家touch到那个感觉即可
中间代码生成
万事俱备,可以生成中间代码了。比如程序
生成的中间代码长这样
如何查看的呢?
前面说了,这样干会生成很多文件,后缀名是.gimple的才是
C语言的中间代码(IR)称为:GIMPLE, 三地址码形式
优化
在编译之前,还要做一件事:优化。我们使用gcc -O配置优化级别,就是在这个阶段完成的
gcc一共提供了5个优化
所有的优化任务
举个例子帮助大家理解编译优化,比如常量折叠,优化前
优化后
看到这,是不是有一种悟的感觉了…
代码生成
代码生成阶段是由代码生成器完成的。代码生成器的职责是:将优化后的中间表示(IR)转换为目标平台的汇编代码
比如代码
生成汇编代码
如果你想查看C语言程序生成的汇编程序,运行时查看反汇编即可,编译时呢?这样查看
这就是C语言代码生成的all
汇编
这个阶段就是将汇编代码编译成机器码
注意,这时候还不是可执行文件,是目标文件.o
如何得到目标文件呢?gcc -c test.s -o test.o
理解这个阶段非常重要,因为我们写很多底层程序,比如操作系统,需要使用汇编+C语言,就是将汇编程序编译成目标文件,C语言程序编译成目标文件,然后链接成可执行文件,才得到真正的操作系统代码
链接
万事俱备,只欠可执行文件了。链接器启动…
执行链接操作:gcc test.o -o test即可生成可执行文件
如果有多个中间文件,可以将多个中间文件链接:gcc test.o 1.o 2.o -o test
注意,gcc完成链接工作,背后其实是调用链接器ld实现的
以上就是C语言程序编译期的全部,你学废了吗?
后续还会更新更多超硬核文章,感兴趣的话可以关注**【硬核子牙】**微信公众号