编译和链接

翻译环境与运行环境

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

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

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

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

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




翻译环境

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

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

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

补充 :在Windows 环境下的目标文件的后缀是.obj,Linux 环境下目标文件的后缀是.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.c和add.c两个文件:

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

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

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




运行环境

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

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

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

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

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

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

程序终止方式包括:

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

  • 意外终止


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

相关推荐
水饺编程5 分钟前
第2章:Linux 基础,解压缩【.tar.bz2】压缩包文件
linux·c语言·汇编
Zwawa36 分钟前
串口命令如何从字符串长成协议:帧头、长度、校验和与状态机
c语言·单片机·嵌入式
小李不想当小白1 小时前
BKP备份寄存器与RTC实时时钟(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
瑞码空间3 小时前
JSP与Servlet核心开发与CRUD实战笔记
java·笔记·servlet·jsp
La Pulga3 小时前
【FreeRTOS源码学习笔记】链表的实现
数据结构·笔记·stm32·mcu·链表·嵌入式·freertos
一滴泪的时间5 小时前
2026RFID 涉密载体管控系统推荐,实现密件全流程闭环管控
经验分享·政务
wuyoutrans6 小时前
以色列专利OA答复翻译,重点要避开哪些坑?
笔记
宝绿特塑业再生科技6 小时前
HDPE再生颗粒户外家具表面粉化机理与改性技术全解析
笔记·户外家具·再生塑料·塑木桌椅
凉、介6 小时前
深入理解 ARMv7-A | Generic Timer
笔记·学习·嵌入式·arm
n 55!w !1087 小时前
MySQL笔记整理2
数据库·笔记·mysql