旨在理解从预处理到链接的过程,不会涉及很底层的东西
预处理
预处理阶段由预编译器参与,主要做了这些事情:
- 头文件的包含,将被包含文件插入到#include的位置,因为文件中可能还包含其他文件,所以这个过程是递归进行的;
- 宏定义的替换,将所有使用到宏的地方用宏内容替换,并且删掉宏定义;
- 条件编译,处理所有条件编译指令,如 #else,#if等等;
- 注释的删除,删掉所有注释
//预处理阶段做的事情是不归属于编译器的,由独立的预处理器进行。
编译
编译是核心,大部分工作都在编译阶段完成,也是本文的重点 编译从整体上看好像就做了一件事:将源代码转换为汇编代码; 为什么需要编译? 因为机器只看的懂0和1组成的序列,只有通过这一过程产生汇编代码,再通过汇编就可以转换为机器语言
但其实编译又可以分为如下六个步骤:
- 扫描
- 语法分析
- 语义分析
- 源代码优化
- 目标代码生成
- 目标代码优化
扫描:将源代码输入到一个扫描器中,扫描器会有一个算法,将源代码字符序列转换为各种记号
- 关键字
- 标识符,放到符号表里面
- 特殊符号
- 字面值(常量)),放到文字表里面
这里的符号表和文字表都是一种数据结构。
语法分析:每个语言都有自己的语法,相应的编译器在语法分析的时候就是按照语言的语法进行分析,因为上一步扫描生成了很多记号,再这里会通过一种算法使用这些记号构建一颗语法树,如果出现语法错误就会报错,例如:括号不匹配,括号使用错了等等。
语义分析:语法正确不一定有意义,例如指针相乘。语义分析会进行类型检查,比如float给int变量赋值,需要进行隐式类型转换,那么在语义分析这里就会进行,有错误的话会报错,注意:语义分析只会分析在编译期间语义能够确定的表达式。
在介绍下一步骤之前先说一下编译器,编译器通常被分为前端和后端,前端包含源码级优化器,后端包含目标代码生成器和目标代码优化器。
源代码优化:这个过程会将源代码中一些可以简化的东西进行执行,比如:
int c=2+3;
这个例子在源代码优化部分就会将2+3转换为5,源代码得到优化。 我们上面说了,在语法分析的时候会建立一颗树,那么在树上面修改内容是很不方便的,所以会产生中间代码。
目标代码生成:源代码已经是最简,那么就会生成目标代码,例如汇编代码。
目标代码优化:这里优化的主要是寻址方式,删除多余指令,使用位运算代替乘法等等。
注意:编译期间并不会在栈上为变量分配空间,而是指定一套内存使用方案,等到运行的时候才拿出来使用这个方案,才真正的为变量在栈上分配空间。
汇编
汇编做的事情比较简单,就是将编译产生的汇编代码转换为机器可以看懂的01序列,这一步骤不需要对指令由删除什么的,只需要对照汇编表翻译就行了。
链接
链接也是很重要的一部分,主要是将文件和库链接在一起。 实际工程中,可能需要多个源文件,如果我在一个源文件中使用了另一个源文件的函数,如果没有链接,那我就无法找到函数的起始地址,也就call失败了,那程序就无法运行,所以链接也很重要。
本文大多数观点都来源于《程序员自我修养》一书,如果你想深入了解不妨去看看此书。
|