C语言:程序执行过程的编译和链接分析

目录

一、翻译环境

 1.预编译(预处理)

2.编译

1)词法分析

2)语法分析

3)语义分析

 3.汇编

4.链接

二、运行环境


在ANSI C的任何一种实现中,存在两个不同的环境,1:翻译环境、2:运行环境

编译是将C语言的源代码(.c文件)转换成机器代码的过程。这个机器代码通常是目标代码(.o文件或.obj文件),还不是最终的可执行文件。编译过程主要包括以下几个阶段:翻译环境:源代码转换成可执行的机器指令(二进制指令)。

运行环境:实际执行的代码

下面的图片清楚的表达了计算机源文件可执行文件的转换各个主要过程。

一、翻译环境

翻译环境:包括编译和链接两个主要的过程。

编译又可分解成预处理(预编译)编译、汇编三个过程。

一个C语言的项目中可能有多个.c文件一起构建,那么多个.c文件如何生成可执行程序呢?

(1)多个.c文件单独经过编译器编译处理生成对应的目标文件

(2)在Windows环境下的目标文件的后缀是.objLinux环境下的目标文件的后缀是.o

(3)多个目标文件链接库一起经过连接器处理生成最终的可执行程序

(4)链接库是指运行时库一个支持程序运行的基本函数集合)或者第三方库

把编译器的三个过程展开来看,就变成了下面的过程:

 1.预编译(预处理)

在预处理阶段,源文件和头文件会被处理成为.i为后缀的文件

gcc环境下想观察以下,对test.c文件预处理后的.i文件,命令如下:

gcc  -E  test.c  -o  test.i

预处理阶段主要处理那些源文件中#开始的预编译指令。比如:#include,#define,处理的规则如下:

(1)将所有的 #define 删除,并展开所有的宏定义

(2)处理所有的条件编译指令,如:#if、#ifdef、#elif、#else、#endif

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

(4)删除所有的注释

(5)添加行号和文件名标识,方便后续编译器生成调试信息等。

(6)或保留所有的#pragma 的编译器指令,编译器后续会使用。

经过预处理后的 .i文件中不再包含宏定义,因为宏已经被展开。并且包含的头文件都被插入到.i文件中。所以当我们无法知道宏定义或者头文件是否正确的时候,可以查看预处理后的.i文件来确认。

2.编译

编译过程的命令:

gcc  -S  test.i  -o  test.s

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

代码优化:对中间代码进行优化,以提高程序运行效率减少程序大小

举个列子理解上述四个过程:

假设有下面的代码:

 array[ index ]  =  ( iddex + 4 ) * ( 2 + 6);

1)词法分析

词法分析:将源代码的字符序列分割成一系列的记号(如关键字、标识符、操作符等),这是编译的基础步骤。

上述程序进行词法分析后得到16个记号:


2)语法分析

语法分析:根据C语言的语法规则,将词法分析产生的记号序列转换成语法树(也称为抽象语法树,AST)。这一步骤检查源代码是否符合语法规则 

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

3)语义分析

语义分析:在语法分析的基础上进一步检查源代码是否语义正确,例如变量是否被定义、类型是否匹配等。

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

 3.汇编

汇编器是将汇编代码转变成机器可执行的指令每一个汇编语句几乎都对应一条机器指令。就是根据汇编指令和机器指令的对照表一一的进行翻译也不做指令优化

gcc  -c  test.s  -o  test.o

4.链接

链接是一个复杂的过程,链接的时候需要把一堆文件链接在一起才能生成可执行程序

链接过程主要包括地址和空间分配符号决议重定位等这些步骤。

链接解决的是一个项目中多文件多模块之间互相调用的问题。

比如:

test文件下有两个源文件,test.c 和 add.c

 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 sum = Add(a, b);
	printf("sum = %d\n", sum);
	printf("g_val = %d\n", g_val);
	return 0;
}

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 变量的地址,所以暂时把调用Add 的指令的目标地址和g_val 的地址搁置。等待最后链接的时候连接器更具引用的符号Add 在其他模块中查找Add 函数的地址,对于全局变量g_val 也是类似的方法来修正地址。这个地址修正的过程也被叫做:重定位

二、运行环境

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值