程序的编译流程
前言
计算机只能处理由0和1序列构成的机器语言,对于使用高级语言编写的文本形式的源代码并不能直接被计算机识别以及执行,因此需要有一个过程用于将程序源代码翻译成可以在机器上执行的二进制指令,这个过程就是程序的编译。
基本编译过程
程序的编译过程可以大致分为四个阶段,依次为预处理、编译、汇编 以及链接,在每个阶段,编译器都会输出相应的中间文件,并作为下个阶段的输入继续处理,直至最终生成可执行文件。一次程序编译的处理过程如下图所示:
在编译的各个阶段需要处理的事务简述如下:
- 预处理:预处理阶段用于处理源代码文件中的预编译指令,包括宏定义、条件编译以及文件包含等,以及添加必要的行号和文件名标识,便于后期调试和错误告警;
- 编译:编译阶段对经过预处理的文件进行一系列的词法分析、语法分析、语义分析以及代码优化后,将程序源码翻译成对应机器的汇编代码;
- 汇编:汇编阶段使用汇编器将汇编代码转变成机器可以执行的指令,并将这些这些指令打包生成可重定位的目标文件;
- 链接:链接阶段的目的就是将汇编阶段生成的所有目标文件及依赖的库文件链接到一起,生成最终的可执行文件。
Linux下使用GCC编译
所有的程序都始于"Hello World",从"Hello World"开始,我们去了解一个最简单的可执行程序是如何生成的。
#include <stdlib.h>
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
在Linux下,使用GCC来编译"Hello World"程序,只要使用如下指令,就可以完成:
gcc hello.c -o hello
上面的指令虽然简单,但是运行的过程中,GCC会依次调用预处理器(cpp)、编译器(cc1)、汇编器(as)和链接器(ld)完成编译的整个流程,因此从本质上来讲,gcc
命令实际上是这些工具的前台包装,它会根据不同的参数要求去调用对应的工具。
隐藏的过程
在执行上述的gcc
命令时,gcc
编译器首先运行C预处理器(cpp),它将C的源程序hello.c
翻译成中间文件hello.i
:
cpp hello.c -o hello.i
接下来,gcc
编译器运行C编译器(cc),它将hello.i
翻译成汇编语言文件hello.s
:
cc -S hello.i -o hello.s
然后,gcc
编译器运行汇编器(as),它将hello.s
翻译成一个可重定位目标文件hello.o
:
as hello.s -o hello.o
最后,gcc
编译器调用链接器(ld),将hello.o
与其依赖的库文件链接成最终的可执行文件:
ld hello.o -o hello -lc \
/usr/lib/crt1.o \
/usr/lib/crti.o \
/usr/lib/crtn.o \
/usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/crtbegin.o \
/usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/crtend.o
至此,所有的编译流程结束。在Linux下使用file
指令查看可执行文件hello
信息,并运行:
相关参考
- 《程序员的自我修养——编译、装载与库》
- 《深入理解计算机系统》