编译过程与原理

编译过程与原理

1. 编译过程 - 编译器在做什么?

如果想要机器按照我们的设想执行我们的命令,我们需要一个能够跟机器沟通的方式,而这种方式也就是==机器语言(第一代计算机语言)==,一堆01构成的指令。我们需要事先设计好指令集到机器上,比如说规定好从某寄存器阅读值的指令为什么。

可这种方式面临的第一个问题就是,不同机器之间指令集不一致,比如说DSP和ARM在实现加法上,可能就会出现不同寄存器的调用方法。那很显然就会设想出一种统一的语言,即==汇编语言(第二代计算机语言)==。而汇编实现的只是将01等机器码指令使用一个统一的命名,如ADD而已。令所有机器指令集可与汇编语言转换即可。

汇编语言仍存在很多问题,主要表现为阅读性极差、开发难度大、开发周期长、移植问题没有彻底解决。因此也就出现了==高级语言(第三代语言)==,在汇编语言的基础上实现了抽象,避免了繁琐寄存器的操作。

那源代码(高级语言)是如何让计算机理解的呢?其步骤是?

  • 如果源代码在操作系统上:源代码生成汇编代码,再通过汇编和链接方式形成可执行文件,然后通过加载器加载到操作系统执行。
  • 如果源代码在虚拟机(解释器)上:源代码生成中间代码(人可以直接理解的,而汇编则认为人无法理解),如字节码等。

在Linux系统下,可用以下指令完成源程序到目标程序的转化:

gcc -o hello hello.c main.c

gcc 编译器驱动程序读取源文件hello.c和main.c,经过预处理、编译、汇编、链接(分别使用预处理器、编译器、汇编器、链接器,这四个程序构成了编译系统)四个步骤,将其翻译成可执行目标程序hello。如下图所示:

alt text

1.1 预处理

预处理器(CPP)根据源程序中以字符”#”开头的命令,修改源程序,得到另一个源程序,常以.i作为文件扩展名。修改主要包括#include、#define和条件编译三个方面。

gcc -o main.i -E main.c

预处理只是对源文件进行了扩展,得到的仍然是C语言源程序。

1.2 编译

编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

编译器(CCL)将经过预处理器处理得到的文本文件hello.i和main.i翻译成hello.s与main.s,其中包含了汇编语言程序,汇编语言程序以一种标准的文本格式确切描述一条低级机器语言指令。 运行以下命令进行编译:

gcc -S main.i hello.i

1.2.1 编译过程

1.3 汇编

汇编器(AS)将hello.s和main.s翻译成机器语言指令,并打包成可重定位目标程序,一般以.o为文件扩展名。可重定位目标程序是二进制文件,它的字节编码是机器语言指令而不是字符。 运行以下指令可得到重定位目标程序main.o和hello.o:

gcc -c main.s hello.s

用文本编辑器打开main.o和hello.o发现文件是乱码,因为此时已经是二进制文件。

1.4 链接

链接程序(LD)将main.o和hello.o以及一些其他必要的目标文件组合起来,创建可执行目标文件。

gcc -o hello main.o hello.o

得到可执行程序hello. 在终端运行./hello,程序加载并运行.

根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:

  • 静态链接 在这种链接方式下,函数的代码将从其所在的静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
  • 动态链接 在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。 对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。

动态链接 动态库的编译