我们从一个简单的hello程序来了解,通过跟踪hello程序的生命周期——从它被程序员创建开始,到系统上运行,输出简单的消息,然后终止。沿着这个生命周期,简要的介绍编译过程,了解这个过程中干了什么。
1 |
|
我们通过编辑器创建并编写了该文件,并取名为hello.c。而这个文件,归根到底是由一串比特来表示的。
但仅仅如此远远不够,因为这个文件只能被我们读懂,机器是无法读懂的。为了运行该程序,每条C语句都必须被其他程序转化为一系列的低级机器语言指令。这些指令按照可执行目标程序的格式打包好,并以二进制磁盘文件的形式存放起来。目标程序称为可执行目标文件。
如何转化?在linux上是由编译器驱动程序完成的,我们可以执行如下指令:
1 | linux> gcc -o hello hello.c |
gcc就是该编译器驱动程序,它读取源程序文件hello.c,并把它翻译成一个可执行目标文件hello。这个具体过程可分为四个阶段完成,如下:
在linux上可以分开进行这四个过程
1 | gcc -E hello.c -o hello.i // 预处理 |
预处理阶段
预处理器(cpp)以字符“#”开头的命令,修改原始的C程序。
比如#include
1 | # 1 "hello.c" |
编译阶段
编译器ccl将文本文件hello.i翻译成文本文件hello.s,它包含了一个汇编语言程序,该程序包含了函数main的定义,如下所示:
1 | main: |
汇编阶段
接下来,汇编器as将hello.s翻译成机器语言指令,这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在文件hello.o中,该文件是一个二进制文件,我们无法直接打开,可以用十六进制编辑器打开。
链接阶段
hello程序调用了printf函数,这个函数是标准C库里的一个函数,printf函数存在一个printf.o的单独的预编译好了的目标文件中。这个文件要合并到hello.o程序中,是通过链接器(ld)负责处理这种合并。结果就得到了hello文件,它是一个可执行目标文件(可执行文件),可以加载到内存中,由系统执行。
参考
本篇主要参考《深入理解计算机系统》