翻译环境和运行环境

在 ANSI C的任何一种实现中,存在两个不同环境

第一种是翻译环境,在这个环境中源代码被转换为可执行的机器指令

第二种是运行环境,用于实际执行代码

翻译环境

翻译环境是由编译和链接两大过程组成的

编译又可以分解成:预处理、编译、汇编三个过程

一个C语言项目中可能有多个.c文件构成,多个.c文件会单独经过编译器,编译处理生

成对应的目标文件,多个目标文件和链接库一起经过链接器处理生成最终的可执

行程序

在 windows 环境下目标文件后缀为.obj, linux 环境下目标文件后缀为.o

预处理

预处理阶段,源文件和头文件会被处理为.i文件

主要处理源文件中 # 开始的预编译指令

  • 将所有 #define 删除,并展开宏定义

  • 处理所有条件编译指令,如:#if、#ifdef、#elif、#else、#endif

  • 处理 #include 预编译指令,将包含的头文件的内容插入到该预编译指令的位置

  • 删除所有注释

  • 添加行号和文件名标识,方便后续编译器生成调试信息

  • 或保留所有的 #pragma 的编译器指令,编译器后续会使用

经过预处理后的.i文件中不再包含宏定义,因为宏已经被展开。并且包含的头文件

都被插入到.i文件
中。所以当我们无法知道宏定义或者头文件是否包含正确的时

候,可以查看预处理后的.i文件来确认。

编译

编译就是将预处理后的文件的进行词法分析、语法分析、语义分析及优化

假如有下列代码

1
2
int arr[10] = { 0 };
arr[0] = 1;

词法分析

将源代码程序输入扫描器,扫描器的任务是简单的进行词法分析,将代码中的字符分割成一系列的记号(关键字、标识符、字面量、特殊字符等)

记号 类型
int 关键字
arr 标识符
[ 左方括号
10 数字
] 右方括号
= 赋值
{ 左大括号
0 数字
} 右大括号
arr 标识符
[ 左方括号
0 数字
] 右方括号
= 赋值
1 数字

语法分析

接下来语法分析器对扫描产生的记号进行语法分析从而产生语法树,这些语法树是以表达式为节点的数


语义分析

由语义分析器来完成语义分析,即对表达式的语法层次分析

编译器做的分析是语义的静态分析,通常包含声明和类型的匹配,类型的转换等,这

个阶段会报告错误的语法信息

汇编

汇编器是将汇编代码转转变成机器可执行的指令,每一个汇编语句几乎都对应一

条机器指令。就是根据汇编指令和机器指令的对照表一一的进行翻译,也不做指

令优化。

链接

链接是一个复杂的过程,需要把一堆文件链接在一起生成可执行程序

主要包括:地址和内存分配,符号决议和重定位等这些步骤

解决的是一个项目中多文件、多模块之间的调用问题

例如现在一个C语言项目中有两个文件 add.c 和 native_c.c

1
2
3
4
5
6
// add.c
int num = 2026;
int Add(int x, int y)
{
return x+y;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// native_c.c
#include <stdio.h>
//test.c
//声明外部函数

extern int Add(int x, int y);
//声明外部的全局变量

extern int num;

int main()
{
int a = 10;
int b = 20;
int sum = Add(a, b);
printf("%d\n", sum);
return 0;
}

native_c.c 经过编译器处理生成 native_c.o

add.c 经过编译器处理生成 add.o

在 native_c.c 文件中使用了 add.c 中的 Add 函数

和 num 变量,必须知道 Add 函数 和 num 变量的地址,所以暂时会把调用 Add 的

指令的目标地址和 num 的地址搁置。等到链接的时候由链接器根据引用的符号

Add 在其他模块中查找 Add 的地址,然后将 native_c.c 所有引用 Add 的指令重新

修正,让他们的目标地址称为真正的 Add 函数的地址,变量 num 类似。这个地址修

正的过程称为重定位