前言
要将编译和链接,就不得不提及编译器是如何运作的,虽然这部分知识是针对于要创造编译器和创作语言的人所需要清楚的,但作为c语言的学习者也需要了解一下,修炼内功,尤其是对于想学习c++的人而言。
编译器的运作过程大致分为翻译环境和运行环境,翻译环境简单的说就是把你写的c语言(c++...)代码翻译成机器理解的二进制序列,并检查基本的语法等错误,最终通过链接生成一个可执行程序,
后缀为exe(windows)。运行化境就是载入内存开始执行程序,一般的越界访问和空指针引用就是在这个阶段发现的。
我们要讲的编译和链接就是在翻译环境中,翻译环境大致分为编译和链接两个过程,而编译又可以细分为预处理,编译,汇编三个过程。
编译
预处理
预处理又可以叫预编译,书籍中也有这种叫法,预处理主要处理那些源文件中#开始的预编译指令。比如:#include,#define,#if,#endif,处理规则如下:
1.将所有的#define删除,并展开所有的宏定义
2.处理所有的条件编译指令,如;#if,#endif....
3.处理#include的预编译指令,将包含的文件内容插入到该预编译指令的位置,也就是展开头文件。这个过程是递归进行的,也就是说被包含的头文件也可能包含其他文件,比如调用main函数之前,就会调用其他函数,也就是说main函数也是被其他函数调用的。
4.删除所有的注释
5.添加行号和文件名标识,方便后续编译器生成调试信息等
6.保留所有的#pragma的编译器指令,编译器后续会使用
在预处理阶段,源文件和头文件会被处理为.i后缀的文件。
经过预处理后的.i文件中不在包含宏定义,因为宏已经被展开。并前包含的头文件都被插入到.i文件中。所以当我们无法知道宏定义或者头文件是否包含正确的时候,可以查看预处理后的.i文件来确认。
编译
编译过程是将预处理后的文件进行一系列的:词法分析、语法分析、语义分析及优化,生成相应的汇编带文件。后缀为.s。
词法分析
将源代码程序输入扫描器,扫描器的任务就是简单的进行词法分析,把代码中的字符分割成一系列的记号(关键字、标识符、字面量、特殊字符)。
语法分析
语法分析用到语法分析器,将对扫描产生的记号进行语法分析,从而产生语法树。这些语法树是以表达式为结点的树。
语义分析
由语义分析器来完成,对表达式的语法层面分析。编译器所能做的分析是语义的静态分析。静态分析通常包括和类型的匹配,类型的转换等。这个阶段会报告错误的语法信息。
符号表
编译阶段会生成一个符号表,符号表的作用主要是为了汇编。
符号表的作用:
1.符号表提供了一些符号的定义信息(如类型、地址),帮助汇编器正确解析符号
2.符号表中记录了符号的地址信息(如函数地址,全局变量地址),汇编器根据符号表为符号分配具体的地址,并生成机器代码。
符号表的作用还有很多,这里简单了解即可,比如:在c++的函数重载就是一个典型例子,c语言规定函数的名字不可以一样。但是在c++,中却可以一样,这就是符号表起了作用,表明编译器在解释c++和c语言的时候,函数的解析规则时不同的,并不只是简单把函数名作为符号表。符号表的生成规则每个编译器都不太同,和编译器有关。
汇编
汇编是通过汇编器将汇编代码转换成机器可执行的指令(二进制),每一个汇编语句几乎都对应一条机器指令。这里不会做指令优化。结束时生成目标文件。
在windows系统下生成的目标文件后缀为.obj,在Linux系统下生成的目标文件后缀为.o。
链接
链接会把多个源文件和头文件链接在一起生成一个可执行程序,这是一个复杂的过程。
链接过程主要包括:地址和空间分配,符号决议和重定位等步骤。
链接解决的是一个项目中多个文件、模块之间相互调用的问题。
比如:函数定义的检查就是在链接阶段检查的
如图,.obj是windows系统下的目标文件,这就是在链接中发现的函数未定义错误。
总结
这里只是简单的介绍了编译器的编译和链接过程,具体的细节并没有提及,当然对于一般的学习者也不用了解的这么深,只用把大致了解就可以了。对于这部分学习是高度抽象的,因为很多部分就是底层机器的运行逻辑,很枯燥,甚至编译这部分单令出来就可以当成一门大学学科了,所以想要把这部分一下子全搞懂,不太现实,只能是干中学,一边写代码一边深入了解。