一、编译系统
以C语言程序为例,一个C语言程序由最初使用C语言编写完成,保存为.c文件。经过预处理,编译,汇编,链接。最终成为一个可执行文件。可以在计算机上执行相应的功能。
其中执行四个阶段的程序,预处理器,编译器,汇编器和链接器一起构成了编译系统。
二、编译过程详解
上述四个过程完成了程序的编译,下面介绍这四个阶段都完成了一个什么样的工作。以最简单的一个C语言程序helloworld程序为例。
#include<stdio.h>
int main()
{
printf("hello,world\n");
}
1、预处理阶段
预处理阶段做了一个什么样的工作呢?我们在编写C语言程序时,都知道需要添加头文件,头文件包含了我们运行一个程序时需要的代码支撑。而预处理阶段就会识别字符#开头的命令,修改原始的C程序,具体到上述代码,预处理器会将stdio.h的内容插入到程序文本中。这样就得到了另外一个C程序,通常是以.i为文件扩展名。
2、编译阶段
编译阶段的工作就是将前面预处理好的.i文件翻译为一个汇编语言程序。通常以.s为文件扩展名。该过程就实现了从高级语言到汇编语言的翻译。
3、汇编阶段
之后,汇编器会将前面的.s文件翻译为机器语言指令,将这些指令打包成为一种叫做可重定位目标程序的格式,并将结果保存在以.o为文件扩展名的目标文件中。在此时可以从上图看出,文件已经变成了一个二进制文件,他的字节编码是机器语言指令而不是字符。此时从汇编语言翻译成了机器语言。
4、链接阶段
前面我们说过汇编后的机器语言指令被打包成了一个可重定位目标程序。这种程序就是为了这一链接阶段。我们通常在设计一个程序的时候不会仅仅涉及到就一个程序,其中背后还会涉及到很多其他的程序。比如最简单的打印helloworld还需要printf函数,而该函数来自于C语言标准库中,我们并没有自主实现该函数。既然用到了该函数就需要他的具体实现。链接完成的工作就是将这些我们需要的文件并入到我们的程序中。之后就得到了一个可执行文件。
至此就可以运行该可执行文件,运行我们的程序查看具体运行结果。
三、Linux系统实践
首先我们需要编写一个程序。就使用上述程序helloworld。
vim hello.c
创建hello.c文件,并在文件中编写如下代码。
编写完成之后文件夹内容如下
1、预处理
gcc -E hello.c -o hello.i
上述命令中-o会多次使用,作用是以某种文件输出,也就是我们使用hello.i保存该命令的结果。-E是将程序处理到预处理阶段就结束,并不直接处理成可执行文件。
运行该命令实现对文件的预处理,使用vim命令,可以看到文件内容中多了几百行内容,也就是前面提到的实现了对头文件的插入。
2、编译
gcc -S hello.c -o hello.s
此命令是在进行编译过程,同理也是用到了-o,作用同上。-S是将程序处理到编译阶段结束,并不继续往下执行汇编和链接阶段。下面两个过程不再赘述。
可以看到结果变成了汇编语言的形式。
3、汇编
gcc -c hello.c -o hello.o
可以看hello.o文件内容变成了乱码。原因在前面已经提到,此时经过汇编过程,该文件结果的字符编码是机器语言指令而不是字符。我们知道在计算机的视野中全部都是二进制01的形式,但是01的形式具体解释起来也是需要看上下文关系,也就是当下文件所处环境,通常而言我们的文本文件他的字符编码是字符,根据ASCII码可以将8个比特看作一个字符,对应到ASCII码表中表示相应的字母或者是符号,是人类可以看懂的。而此时他不是代表的字符,而是机器语言指令。相当于是同一个东西使用两种不同的规则进行解释。含义自然不同。
4、链接
gcc -o hello hello.c
该命令中只有一个-o该含义同上,是指定输出目标文件的名称。前面我们使用的-E -S -c 都是相当于将整个编译过程进行截断,运行到某一步停止。此时只需要让他运行完整个过程就可以。因为最后的链接阶段结束就输出了一个可执行文件。
可以看到得到了一个叫做hello的可执行文件。
./hello
直接执行即可得到程序运行结果。