程序员的自我修养(整理)

编译与链接

在linux下,使用gcc编译程序的时候,需要经历四步过程,分别是预处理,编译,汇编和链接

编译器流程

1
2
3
4
5
6
7
graph TD
A[Source Code] -->|Scanner| B(Tokens)
B --> |Parser| C(Syntax Tree)
C --> |Semantic Analyzer| D(Commented Syntax Tree)
D --> |Source Code Optimizer| E(Intermediate Representation)
E --> |Code Generator| F(Target Code)
F --> |Code Optimizer| G(Final Target Code)

预处理

预处理过程主要处理源代码中以“#”开始的预编译指令。比如“#include”,“#define”等,主要处理规则如下:

  1. 将所有的“#define”删除,并且展开所有的宏定义。
  2. 处理所有条件预编译指令,比如:“#if”,“#ifdef”,“#elif”,“#else”,”#endif”。
  3. 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置,注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
  4. 删除所有的注释“\\“和”/**/“。
  5. 添加行号和文件标识名,比如#2”hello.c“ 2,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时能够显示行号.
  6. 保留所有的#prcagma编译器指令,因为编译器需要使用它们。

经过预处理的.i文件不包含任何宏定义,因为所有的宏已经被展开。

编译

编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析以及优化后生产相应的汇编代码。

举一个例子:

1
2
array[index] = (index + 4) * (2+6)
CompilerExpression.c

词法分析

源代码程序被输入到扫描器,运用类似于有限状态机的算法将源代码的字符序列分割成一系列的记号(token)。

1
记号(token)分为如下几类:关键字,标识符,字面量(包含数字,字符串等)和特殊符号(如加号,等号)

识别记号的同时,扫描器也完成了其他工作,例如将标识符存放到符号表,将数字,字符串常量存到文字表等。

lea程序实现词法扫描,按照用户之前描述好的词法规则,将输入的字符串分割成一个个记号。

语法分析

语法分析器对由扫描器产生的记号进行语法分析,从而产生语法树(以表达式为节点的树)。整个过程采用了上下文无关语法。

在语法分析的同时,运算符号的优先级和含义也被确定。

如果出现了表达式不合法,比如各种括号不匹配,表达式中缺少操作符等,编译器就会报告语法分析阶段的错误。(语法分析,也就是检测表达式格式正确与否)

yacc工具根据给定的语法规则,生成语法树。

1
2
3
4
5
6
7
8
9
10
11
graph TD
A[Assign Expression =] -->B(Subscript Expression )
A[Assign Expression =] -->C(Multiplicativer Expression *)
B(Subscript Expression ) --> D(Identifier array)
B(Subscript Expression ) --> F(Identifier Index)
C(Multiplicativer Expression *) --> G(Addtive Expression +)
C(Multiplicativer Expression *) --> H(Addtive Expression +)
G(Addtive Expression +) --> I(Identifier Index)
G(Addtive Expression +) --> J(Number 4)
H(Addtive Expression +) --> K(Number 2)
H(Addtive Expression +) --> L(Number 6)

语义分析

检测这个表达式是否真的有意义。例如两个指针做乘法运算是没有意义的,但是在语法分析里,语法上是合法的。

编译器所能分析的语义是静态语义(staticSemantic),所谓静态语义是指在编译期可以确定的语义。包括声明和类型的匹配,类型的转换。

与之对应的是动态语义,只有在运行期才能确定。

汇编

汇编器将汇编代码转为机器码的过程

链接

菜鸡写不出来,呜呜,还是等详细看完再写吧

image-20200225185321610