目录
[2、 翻译环境:预编译+编译+汇编+链接](#2、 翻译环境:预编译+编译+汇编+链接)
1、翻译环境和运行环境
在ANSIC的任何一种实现中,存在两个不同的环境:
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码。

2、 翻译环境:预编译+编译+汇编+链接
翻译环境是如何将源代码转换为可执行的机器指令的呢?这需要深入了解翻译环境的工作流程。翻译环境主要由编译 和链接两大阶段组成,其中编译过程又可细分为三个步骤:预处理(部分资料称为预编译)、编译和汇编。

在C语言项目中,当多个.c文件需要一起构建时,生成可执行程序的过程分为两个主要步骤:
1、编译阶段:
- 每个.c文件会单独进行编译处理,生成对应的目标文件
- 注意:Windows环境下目标文件扩展名为.obj,Linux环境下为.o
2、链接阶段:
- 所有目标文件将与链接库一起通过链接器处理
- 最终生成可执行程序
- 链接库包括运行时库(提供程序运行所需的基本函数)和第三方库
如果再把编译器展开成3个过程,那就变成了下面的过程(用gcc为例,拆解编译链接的过程):

2.1、预处理
在预处理阶段,源文件和头文件会被转换为以.i为后缀的中间文件。若要在gcc环境下查看test.c文件预处理后的结果,可执行以下命令:
cpp
gcc -E test.c -o test.i
预处理阶段主要处理源文件中以#开头的预编译指令,例如#include、#define等,具体处理规则如下:
- 移除所有#define指令并展开宏定义
- 处理条件编译指令(#if、#ifdef、#elif、#else、#endif)
- 递归处理#include指令,将被包含的头文件内容插入到对应位置
- 删除所有注释内容
- 添加行号和文件名标识,便于编译器生成调试信息
- 保留#pragma指令供编译器后续使用
预处理生成的.i文件具有以下特点:
- 不再包含宏定义(已全部展开)
- 所有头文件内容已插入到相应位置
- 可通过检查.i文件来验证宏定义和头文件包含的正确性
2.2、编译
编译过程对预处理后的文件执行一系列操作:包括词法分析、语法分析、语义分析及优化处理,最终生成对应的汇编代码文件。相关编译命令如下:
cpp
gcc -S test.i -o test.s
编译以下代码时会发生什么情况?假设现有如下代码片段:
cpp
array[index] = (index+4)*(2+6);
2.2.1 词法分析:
源代码程序被输入扫描器后,扫描器执行词法分析任务,将代码中的字符序列分割成一系列标记(包括关键字、标识符、字面量和特殊字符等)。经词法分析后,上述程序共生成16个标记。

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

2.2.3 语义分析
由语义分析器来完成语义分析,即对表达式的语法层面分析。编译器所能做的分析是语义的静态分析。静态语义分析通常包括声明和类型的匹配,类型的转换等。这个阶段会报告错误的语法信息。

2.3、汇编
汇编器是将汇编代码转转变成机器可执行的指令,每⼀个汇编语句几乎都对应一条机器指令。就是根据汇编指令和机器指令的对照表⼀⼀的进行翻译,也不做指令优化。 汇编的命令如下:
cpp
gcc -c test.s -o test.o
2.4、链接
链接是将多个文件组合生成可执行程序的复杂过程。其主要步骤包括:地址与空间分配、符号决议以及重定位等。链接的核心作用是解决项目中多文件、多模块间的相互调用问题。比如: 在一个C的项目中有2个.c文件( test.c 和 add.c ),代码如下:

cpp
//test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//test.c
//声明外部函数
extern int Add(int x, int y);
//声明外部的全局变量
extern int g_val;
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a,b);
printf("%d",c);
return 0;
}
cpp
//add.c
#define _CRT_SECURE_NO_WARNINGS 1
int g_val = 2022;
int Add(int x, int y)
{
return x + y;
}
编译过程中,每个源文件都是独立编译生成对应的目标文件。例如:
- test.c 编译生成 test.o
- add.c 编译生成 add.o
当 test.c 文件中调用 add.c 中的 Add 函数和使用 g_val 变量时,由于文件是单独编译的,编译器在处理 test.c 时无法确定这些符号的实际地址。因此:
- 编译器会暂时记录调用 Add 函数的指令目标地址和引用 g_val 的地址
- 这些地址信息会被标记为待定状态
在最终链接阶段,链接器会:
- 根据符号表在其他模块中查找 Add 函数和 g_val 的实际地址
- 对 test.c 中所有引用 Add 和 g_val 的指令进行地址修正
- 将目标地址更新为正确的实际地址
这个地址修正的过程称为"重定位"。

3、运行环境
程序执行的基本流程如下:
-
内存加载阶段(程序必须载入内存中 ):
- 在操作系统环境中,通常由操作系统自动完成程序加载
- 在独立环境中,需要手动加载程序,或通过将可执行代码写入只读内存实现
-
程序启动阶段:
- 程序开始执行后,首先调用main函数
- 正式进入程序代码执行流程
-
运行时内存管理:
- 程序使用运行时堆栈(stack)存储函数的局部变量和返回地址
- 同时使用静态内存(static)存储变量,这些变量的值在整个程序执行期间保持不变
-
程序终止阶段:
- 正常终止:通过main函数返回实现
- 异常终止:程序意外终止的情况