✨✨欢迎大家来到Celia的博客✨✨
🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉
所属专栏:C语言
目录
引言
在C语言代码进行实现的时候,都存在两个不同的环境。一个是翻译环境,在这个环境中C语言的源代码会被翻译成可以执行的机器指令,也就是二进制指令。另一个是执行环境,用于执行这些被翻译后的机器指令。本篇文章将会详细介绍在翻译环境中所进行的一系列处理过程。
一、翻译环境
翻译环境由编译和链接两个过程所组成。在这其中,编译又可以分为预处理(预编译)、编译、汇编三个过程。
在一个C语言的项目中,有可能会有多个.c文件,这些文件是如何生成可执行的程序的呢?
- 多个.c文件会单独经过编译器,形成多个以.obj为后缀的目标文件(VS环境)。
- 多个目标文件和链接库一起通过链接器,处理后形成后缀为.exe的可执行程序
如果把编译拆分成三个过程,如下图所示(gcc编译器):
1.1 编译
1.1.1 预处理
在这个过程中,源文件和头文件会被处理成为以.i为后缀的文件,在gcc编译器上,如果想进行该过程,指令如下:
gcc -E text.c -o text.i
在预处理阶段,会进行以下操作:
- 将所有的#define删除,并且展开所有的宏定义。
- 处理所有的条件编译指令。(#if、#ifdef、#elef等)
- 处理#include预编译指令,将该头文件所包含的所有内容插入到预编译指令所在的位置。这个过程中,如果头文件中包含了其他的头文件,将会递归进行展开。
- 删除所有的注释信息。
- 添加行号和文件名标识,以便后续的调试。
- 保留所有的#pragma的编译器指令,以便后续使用。
1.1.2 编译
在该过程中,会对文件进行一系列的词法分析、语法分析、语义分析及优化,生成相应的汇编代码文件。
在gcc编译器上的指令:
gcc -S text.i -o text.s
下面以一个例子来解释该过程:
array[index] = (index+4)*(2+6);
1.1.2.1 词法分析
在该过程中,源代码程序会被输入到扫描器中,进行简单的词法分析,把代码中的字符分割成一系列的记号:
1.1.2.2 语法分析
在该过程中,语法分析器会对扫描产生的记号进行语法分析,产生一个语法树,语法树是以表达式为节点的树。
1.1.2.3 语义分析
该过程由语义分析器完成语义分析,是对表达式语法层面的分析(语义静态分析),语义静态分析通常包括:声明、类型的匹配、类型的转换等。这个阶段会报告错误的语法信息。
1.1.3 汇编
汇编器会将汇编代码转换为机器可执行的机器指令,每一条汇编指令对应一条机器指令。这个过程是根据汇编指令和机器指令对照表一一进行翻译,不会做任何的优化。
在gcc编译器上的指令:
gcc -c text.s -o text.o
1.2 链接
在这个过程中,会进行地址和空间的分配,符号决议和重定位等步骤。目的是将一个项目中的多个文件链接在一起 ,形成可执行程序。
关于重定位:
如果我们在text.c文件中用到了其他文件中的变量或者函数,按道理来讲,需要明确这些在其他文件中的变量和函数的实际地址,才能够使用它们。但是因为每个文件都是单独编译的,text.c文件在被编译的时候并不知道其他文件中的变量和函数的地址,编译器会暂时搁置它们的地址。等到最后链接的时候,由链接器根据函数名或变量名,在其他的模块中寻找这些函数或者变量的地址,然后将之前搁置的地址重新修正,让这些暂时被搁置的地址变成真正的函数或变量的地址。这个暂时搁置地址在后期进行重新修正的过程就叫做重定位。
二、运行环境
- 程序一般必须载入内存中才能运行。在有操作系统的环境中,一般由这个操作系统来完成。 如果没有操作系统,程序的载入必须由手工安排,或者通过可执行代码置入只读内存来完成。
- 程序运行后,进入main函数
- 执行代码,使用运行堆栈储存局部变量和地址,也可使用静态内存。
- 终止程序。正常终止main函数/异常终止。