3.连接和编译基本概念

3.连接和编译基本概念

Posted by ZhaoLe on February 19, 2021

写在前面

在学习计算机组成原理的过程中对连接,装载进行更近一步的扩展学习,参考《程序员的自我修养—连接,装载与库》进行阅读笔记

命令行中输入一行gcc func.c命令,将源代码编译成可运行文件,看似简单的一句话,其实包含了一整套的流程,分别是:预编译,编译,汇编,链接。

func.c -> cpp -> func.i -> cc1 -> func.s -> as -> func.o -> ld -> func(可执行文件)

预编译:一般处理代码中包含#的预编译指令。 编译:把预编译文件进一步进行词法分析,语法分析,语义分析,优化后产生汇编文件 汇编:就是将汇编代码文件中的汇编语言转换成机器可执行指令。每一个汇编指令都对应一个机器指令。 链接:处理多文件模块间的相互引用如:变量,函数等,使的各个模块间能正确衔接。

编译

编译器整个流程分为:词法分析,语法分析,语义分析,源代码优化,代码生成,目标代码优化。

以一个简单的例子开始说明:

1
array[index] = (index + 4) * (2 +8)
  • 词法分析:根据一套算法将源代码分割成一系列记号(Token),词法分析器扫描源代码文件,按照关键字,标识符,字面量,特殊符号进行分类。并且在扫描的过程也会把标识符存放到符号表,数字和符串常量存放到文字表等,以备后面步骤使用 经过分析,上面源代码被拆分为 array,[,index,],=,(,index,+,4,),*,(,2,+,8,)这些记号。

  • 语法分析:对词法分析出的记号进行语法分析,形成以表达式为节点的语法树。整个过程采用上下文无关语法的分析手段,在语法分析的同时 运算表达式的优先级也会被确定下来。 1

  • 语义分析:语法分析主要分析这个表达式是否合规,但是不了解这个语句是否真有意,比如指针和整型数相乘在语法上是没有问题的。具体语义上就行不通了。 语义分析分静态分析和动态分析,编译器使用的是静态语义分析,源代码在编译期间可以确定的语义。静态语义分析包括声明,类型匹配和转换。比如就是隐式类型转换就是其中一个。 另外一个动态语义分析是在运行中进行分析,比如:两个数相除,且0是除数就是运行期间语义错误。

1

  • 中间语言生成: 对源码进行优化,不同编译器有不同的定义,例子中2+8就可以在编译期间优化直接合并成结果。由于在语法树上直接做源码优化比较困难,所以源码优化器先是根据语法树的顺序转换成中间代码再进行优化,中间代码很接近目标代码,但是跟目标机器和运行时环境是无关的,它不会包含变量地址和寄存器等等(感觉可以看成一种伪代码)。 中间代码的出现使的编译器可以分前,后端,前端负责处理分析与机器无关的中间代码(词法,语法,语义分析等),后端将中间代码转换成目标代码并且进行优化等。这样对于跨平台的编译器来说可以针对不同平台使用同一个前端和不同机器平台的数个后端。

1

  • 目标代码生成和优化: 代码生成器将中间代码转换为机器代码,不同目标机器有不同的转换方式,代码优化器对指令进行优化,比如选择合适的寻址方式,使用位移来代替乘法运算,删除多余指令等。

再经过上面一系列处理后源代码编译成目标代码。但是其中还有部分的地址没有被确定。在多模块下有全局变量或者不同模块下的局表变量在编译期间是不能确定地址的。 编译器将源码编译成没有连接的目标文件,在最终连接阶段。再将目标文件连接成可执行文件

连接

一个项目由N多个模块组成,每个模块间有函数调用,参数调用等。每个模块也可以单独进行编译,测试等,最后将这些模块组成在一起形成一个可运行的项目则是链接过程该干的事,知道了调用函数或者变量的地址(也就是模块间符号的引用),模块间通过符号进行通信最终连接在一起。

连接分地址和空间分配,符号解析,重定向等步骤 每个模块的源代码文件经过编译器编译为目标文件,再将多个目标文件和库一起连接起来成为可执行文件。

写在后面

上面主要介绍了源代码到目标代码所经历的过程,其中最主要的就是编译和连接。不过编译复杂的可以单独出本书了,目前看计算机组成原理主要是介绍连接这块的概念,这篇文章也就对编译过程有个初步的概念了解,后面会专门对连接进行学习扩展。