引言
在C语言中,编译和链接是将源代码转换为可执行程序的两个关键步骤,理解它们的重要性有助于更好地掌握整个软件的构建过程。在本篇文章中,我将详细介绍C语言背后编译与链接的原理,让我们一起开始学习吧!!
一.翻译环境与运行环境
翻译环境(Translation Environment)和运行环境(Runtime Environment)是两个与编程和软件开发相关的概念,它们分别描述了代码从源码到可执行程序的转换过程中所处的不同阶段。
1.翻译环境:
翻译环境指的是将高级语言编写的源代码转换成机器能够理解并执行的低级代码(通常是机器码或字节码)所需的一系列工具和设置。这个过程涉及到编译、链接等步骤,并且可能包括预处理、汇编等其他操作。
2.运行环境:
运行环境则指在软件被成功翻译后,实际上运行该软件时所依赖的系统配置、库文件以及其他资源。它确保了程序可以正常地在目标设备或平台上执行。例如,在Java中,JVM(Java虚拟机)提供了一个跨平台一致性很强的运行时环境;而对于C/C++这样直接编译为本地机器码的语言,则需要确保有合适版本的动态链接库(DLLs)或共享对象(SOs)来支持程序运行。
简单来说,翻译环境关注如何把人类可读写易懂得高层次计算机语言变成计算机可以直接理解并且执行得底层表示;而运行环境则关注已经被翻译好得程序如何在特定硬件和操作系统上顺利地被加载、执行,并与外部进行交互。
———————————————————————————————————————————本文,我们将详细介绍翻译环境下的编译和链接两大过程。
二.编译
编译可以分解成预处理,编译和汇编三个过程。
预处理
预处理阶段主要处理那些源文件中#开始的预编译指令。处理规则如下
- 将所有#define删除,并展开所有的宏定义
- 处理所有条件编译指令,如:#if ,#ifdef ,#elif , #else , #endif
- 处理#include预编译指令,将包含的头文件的内容插入到该预编译指令的位置
- 删除所有注释
- 添加行号和文件名标识,方便后续编译器生成调试信息
- 保留所有的#pragma的编译器指令
预处理后得到.i文件
编译
编译过程就是将预处理后的文件进行一系列的词法分析,语法分析,语义分析及优化,生成相应的汇编代码文件。
词法分析:将源代码程序输入扫描器,将代码中的字符分割成一系列的记号(关键词,标识符,字面量,特殊字符等)
语法分析:语法分析器将对扫描产生的记号进行语法分析,产生语法树
语义分析:语义分析器对表达式的语法层面进行分析。编译器所能做的分析是语义的静态分析。静态语义分析包括声明和类型的匹配,类型的转换等。这个阶段会播报错误的语法信息。
编译后得到.s文件
汇编
汇编器将汇编代码转变成机器可执行的指令,每一个汇编语句都对应一条机器指令。汇编就是根据汇编指令和机器指令的对照表一一翻译,也不做指令优化。
三.链接
链接解决的是一个项目中多文件、多模块之间互相调用的问题。链接过程主要包括:地址和空间分配,符号决议和重定位这些步骤。
举例:在一个C语言项目中有两个.c文件(test.c和add.c)
//add.c文件
int g_val = 2024;
int Add(int x, int y)
{
return x+y;
}
//test.c文件
#include <stdio.h>
extern int Add(int x, int y);//声明外部函数
extern int g_val;//声明外部的全局变量
int main()
{
int a = 10;
int b = 20;
int sum = Add(a,b);
printf("%d\n",sum);
return 0;
}
我们在test.c文件中每一次使用Add函数和g_val时必须确切地知道Add和g_val的地址。
但是由于每个文件是单独编译的,在编译器编译test.c时并不知道Add函数和g_val变量的地址,所以暂时把调用Add的指令的目标地址和g_val的地址搁置。
链接的时候,由链接器根据引用的符号Add在其他模块中查找Add函数的地址,然后将test.c中所有引用Add的指令重新修正,让他们的目标地址为真正的Add函数的地址,对于g_val也用类似的方法来修正。
这个地址修正的方法也叫重定位。
以上就是编译和链接的大致过程啦,如果大家想要更加详细了解其背后的原理,可以参考《程序员的自我修养》一书~ 感谢大家观看,给博主点个赞吧!