编译和链接

翻译环境与运行环境

在 ANSI-C 的任意实现中,存在两种不同的环境:

  • 翻译环境:在此环境中,源代码被转换为可执行的机器指令(二进制代码)。

  • 运行环境:用于实际执行代码的环境。

由于 C 语言代码是以文本形式编写的,计算机无法直接理解,因此必须将这些文本信息翻译成机器指令。

这就好比一位只会中文的中国人和一位只会英语的美国人想要交流,必须借助翻译作为媒介;同样,要想用文本指令控制计算机,也必须借助像gcc这样的"翻译官"。




翻译环境

那么,翻译环境是如何将源代码转换为可执行的机器指令的呢?实际上,翻译环境包括 编译链接 两大过程,而 编译 又可进一步分解为:预处理编译汇编 三个步骤。

在一个 C 语言项目中,可能有多个.c文件共同构建。这些.c文件会先单独经过编译器处理,生成各自对应的目标文件

随后,这些目标文件与所需的链接库 (如:动态库、静态库)一起,由链接器处理,最终生成可执行程序。(库是支持程序运行的基本函数集合)

补充 :在Windows 环境下的目标文件的后缀是.objLinux 环境下目标文件的后缀是.o

如果进一步展开编译器的三个步骤,整个过程如下所示



1. 预处理(Preprocessing)

预处理阶段主要处理源文件中以 # 开头的预编译指令。在该阶段,源文件和头文件会被处理成以
.i 为后缀的文件。

使用gcc观察预处理后的文件,可执行以下命令:

Shell 复制代码
gcc -E test.c -o test.i

预处理规则:
  • 删除所有#define并展开所有宏定义。

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

  • 处理#include指令,将被包含的头文件内容插入到该预编译指令的位置。这个过程是递归进行的,也就是说被包含的头文件也可能包含其他文件。

  • 删除所有注释。

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

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

预处理后的.i文件不再包含宏定义,因为宏已经被展开。且所有包含的头文件内容已被插入到.i文件中。所以若需确认宏定义或头文件包含是否正确,可查看预处理后的.i文件。



2. 编译(Compilation)

编译过程将预处理后的文件进行词法分析语法分析语义分析及优化,生成相应的汇编代码文件。

编译命令如下:

Shell 复制代码
gcc -S test.i -o test.s

以以下代码为例:

Shell 复制代码
array[index] = (index + 4) * (2 + 6);

词法分析

源代码被输入扫描器,进行词法分析,将代码中的字符分割为一系列记号(如关键字、标识符、字面量、特殊字符等)。上述代码经词法分析后,得到 16 个记号:

记号 类型
array 标识符
[ 左方括号
index 标识符
] 右方括号
= 赋值
( 左圆括号
index 标识符
+ 加号
4 数字
) 右圆括号
* 乘号
( 左圆括号
2 数字
+ 加号
6 数字
) 右圆括号

语法分析

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


语义分析

语义分析器进行语义分析,即对表达式进行静态语义检查,包括声明与类型的匹配、类型转换等。此阶段会报告语法错误。



3. 汇编(Assembly)

汇编器根据汇编指令和机器指令的对照表,将汇编代码转变为机器可执行指令,每个汇编语句几乎都对应一条机器指令。此过程不进行指令优化。

汇编命令如下:

Shell 复制代码
gcc -c test.c -o test.o


4. 链接(Linking)

链接是一个复杂的过程,涉及地址与空间分配符号决议重定位等步骤。其主要解决多文件、多模块间的相互调用问题。

例如,一个项目中包含test.cadd.c两个文件:

  • test.c经编译生成test.o

  • add.c经编译生成add.o

test.c中使用了add.c中的Add函数和全局变量g_val,在编译test.c时,编译器并不知道Addg_val的实际地址,因此会暂时搁置这些符号的地址。链接时,链接器会在其他模块中查找这些符号的地址,并将test.c中所有引用这些符号的指令重新修正为正确的地址。此过程称为**"重定位"**。




运行环境

程序执行前必须先载入内存:

  • 在有操作系统的环境中:通常由操作系统完成载入。

  • 在独立环境中:需手动安排载入,或将可执行代码置入只读内存(如:单片机烧录)。

程序执行始于main函数的调用。执行过程中,程序使用:

  • 运行时堆栈(Stack):存储局部变量和返回地址。

  • 静态内存(Static):存储静态变量,其值在整个程序执行期间保持不变。

程序终止方式包括:

  • 正常终止(main 函数返回)

  • 意外终止


以上简要介绍了 C 程序从编译链接到最终执行的整个过程。若希望深入了解目标文件格式(如:.elf)、链接的底层实现(如:空间分配、符号解析与重定位等),推荐阅读**《程序员的自我修养》**一书。

相关推荐
aramae4 小时前
MySQL数据库入门指南
android·数据库·经验分享·笔记·mysql
chenzhou__4 小时前
LinuxC语言文件i/o笔记(第十七天)
linux·c语言·笔记·学习
chenzhou__4 小时前
LinuxC语言文件i/o笔记(第十八天)
linux·c语言·笔记·学习
01100001乄夵4 小时前
FPGA模块架构设计完全入门指南
经验分享·笔记·学习方法·fpga入门·fpga学习之路
01100001乄夵4 小时前
FPGA零基础入门:Verilog语法攻略
经验分享·笔记·学习方法·fpga入门·fpga学习之路
受之以蒙4 小时前
Rust ndarray 高性能计算:从元素操作到矩阵运算的优化实践
人工智能·笔记·rust
霜绛5 小时前
Unity:lua热更新(一)——AB包AssetBundle、Lua语法
笔记·学习·游戏·unity·lua
霜绛5 小时前
Unity:lua热更新(二)——Lua语法(续)
笔记·学习·unity·游戏引擎·lua
2301_800399725 小时前
c snprintf sizeof遇到的问题
c语言·stm32