以下是一个 test.c 程序:
#include <stdio.h>
int main() {
printf("hello, world\n");
return 0;
}
在 Unix 系统上,由编译器把源文件转换为目标文件
gcc -o hello hello.c
这个过程如下:
(1)预处理阶段
预处理器(cpp)根据以字符#开头的命令(directives),修改原始的C程序。比如hello.c中第一行的
#include <stdio.h>指令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入到程序文本中
去。结果就得到了另一个C程序,通常是以.i作为文件扩展名。
总结:处理以 # 开头的预处理命令
(2)编译阶段
编译器(cc1)将文本文件 hello.i翻译成文本文件 hello.s,它包含一个汇编语言程序。汇编语言程序
中的每条语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。汇编语言是非常有用
的,因为它为不同高级语言的不同编译器提供了通用的输出语言。例如,C编译器和Fortran 编译
器产生的输出文件用的都是一样的汇编语言。
总结:将.i文件翻译成汇编文件
(3)汇编阶段
汇编器(as)将hello.s 翻译成机器语言指令,把这些指令打包成为一种叫做可重定位(relocatable)
目标程序的格式,并将结果保存在目标文件 hello.o 中。hello.o文件是一个二进制文件,它的字节
编码是机器语言指令而不是字符。如果我们在文本编辑器中打开hello.o文件,呈现的将是一堆码。
总结:将汇编文件翻译成可重定位目标文件
(4)链接阶段
请注意,我们的 hello程序调用了 printf 函数,它是标准C库中的一个函数,每个C编译器都提
供。printf 函数存在于一个名为printf.o的单独的预编译目标文件中,而这个文件必须以某种方式
并入到我们的hello.o程序中。链接器(ld)就负责处理这种并入。结果就得到hello文件,它是一
个可执行目标文件(或者简称为可执行文件)。可执行文件加载到存储器后,由系统负责执行。
总结:将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执
行目标文件。
图示结果如图:
总结:
对于一个.c文件:
第一步:预处理阶段。预处理器(cpp)处理.c文件中以#开头的预处理命令,得到.i文件;
第二步:编译阶段。编译器(cc1)将.i文件处理成汇编文件.s;
第三步:汇编阶段:汇编器(as)将汇编文件.s翻译成可重定位目标文件.o;
可重定位目标文件:
包含 二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个
可执行目标文件。
第四步:链接阶段。连接器(ld)将可重定位目标文件.o等单独编译好的目标文件进行合并,得到
可执行文件。
那么怎么得到每个阶段的文件呢?
(1)得到预处理后的文件.i
gcc -E test.c -o test.i
(2)得到编译后的文件.s
gcc -S test.c -o test.s
查看
(3)得到汇编后的文件.o
gcc -c test.c -o test.o
(4)得到链接后的文件(可执行文件)
从.o文件得到可执行文件
一气呵成