想听听 .c 到 .exe 的故事吗?

时间:2023-02-05 08:56:29

欢迎来到 Claffic 的博客 ????????????

前言:

在用C语言写代码前,我们会先创建一个或多个源文件(.c 文件),最终源文件会变成可执行文件(.exe 文件),你知道这期间经历了什么吗?听我细细讲解 ~


目录

????Part1. 程序的翻译环境和执行环境

????Part2. 编译与链接

2.1 翻译环境

2.2. 编译的几个阶段

2.2.1 预编译

2.2.2 编译

2.2.3 汇编

2.3 链接


Part1. 程序的翻译环境和执行环境

ANSI C 的任何一种实现中,存在两个不同的环境:  
第一 种是 翻译环境  ,在这个环境中 源代码被转换为可执行的机器指令
种是 执行环境 ,它用于 实际执行代码

想听听 .c 到 .exe 的故事吗? 简单的图示 

接下来我会给大家依次讲解这两个过程

 

Part2. 编译与链接

2.1 翻译环境

想听听 .c 到 .exe 的故事吗?

如图,每个 源文件 单独经过 编译器 处理,生成 目标文件 ,所有目标文件与 链接库 一起,在 链接器 的作用下生成 可执行文件

演示:

创建一个项目,在这个项目下创建三个源文件

想听听 .c 到 .exe 的故事吗?

内容如下:

//test.c
#include<stdio.h>

extern Add(int, int);
extern Sub(int, int);

int main()
{
	int a = 20;
	int b = 30;
	int c = Add(a, b);
	int d = Sub(a, b);

	printf("%d\n", c);
	printf("%d\n", d);

	return 0;
}
//Add.c
int Sub(int x, int y)
{
	return x + y;
}
//Sub.c
int Add(int x, int y)
{
	return x - y;
}

运行后,

 查看 .exe 文件:

想听听 .c 到 .exe 的故事吗?

 查看 .obj 文件:

想听听 .c 到 .exe 的故事吗?

想听听 .c 到 .exe 的故事吗?

是不是与上述过程一样呢 

一些细节:

• 组成一个程序的每个源文件通过编译过程分别转换成目标代码object code);

• 每个目标文件由链接器linker)捆绑在一起,形成一个单一而完整的可执行程序;

• 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员      个人的程序库,将其需要的函数也链接到程序中。

2.2. 编译的几个阶段

我们已经知道了,源文件变成可执行文件需要编译和链接两个过程,那么编译本身有哪些阶段呢?

想听听 .c 到 .exe 的故事吗?

我是图示 

由图示可看出:编译经历的阶段有 预编译 --> 编译 --> 汇编 

接下来细讲每个过程:

注:由于 VS2022 属于集成开发环境,不方便展示细节,

       所以接下来我会使用 Linux 环境下的 gcc 来演示编译和链接的过程。

为方便起见,我们只使用 test.c 和 Add.c 文件

2.2.1 预编译

首先把刚才的代码写入文件:

想听听 .c 到 .exe 的故事吗?

这时我们只进行预编译

输入指令: gcc test.c -E  (-E 就是预编译后停下来)

回车

会发现 预编译的结果直接输出在屏幕上了:
想听听 .c 到 .exe 的故事吗? 其实内容非常多,这里选取部分

我希望把 预编译的内容放在一个文件中 

输入指令: gcc test.c -E -o test.i 

想听听 .c 到 .exe 的故事吗?

 此时就多了一个 test.i 文件

对 Add.c 进行相同的操作   gcc Add.c -E -o Add.i 

打开相应的文件  vim test.i 

有足足800多行代码: 

想听听 .c 到 .exe 的故事吗?

这时会问了: 为什么预编译后出现了这么多代码? 

回想一下,我们的 test.c 里面有什么内容来着?

 #include<stdio.h> 

没错,就是引了头文件,可以理解为: 在预编译过程中将相关的头文件进行了展开 

于是就有了预编译的第一条操作:

• 头文件的包含

接下来我在 test.c 文件中加入以下代码: 

想听听 .c 到 .exe 的故事吗?

重新预编译 test.c 到 test.i 中:
想听听 .c 到 .exe 的故事吗?

 可以发现:MAX被替换,注释也消失了;

• define 定义符号的替换

• 注释删除

小总结:

预编译阶段中进行的操作:

• 头文件的包含

• define 定义符号的替换

• 注释删除

      都属于 文本操作

2.2.2 编译

我们接下来要做的就是让预编译后的文件只进行编译操作

输入指令: gcc test.i -S 

想听听 .c 到 .exe 的故事吗?可见生成了一个 test.s 的文件

打开 test.s :

想听听 .c 到 .exe 的故事吗?

可见里面是一些 汇编代码 

其实这就是编译阶段的主要操作:

• 把C语言代码翻译成汇编代码

再深入些,

翻译的过程有:

1. 语法分析

2. 词法分析

3. 语义分析

4. 符号汇总

其实前三个过程比较好理解,就像我们读英语一样,要把英语转换成中文来理解。像这些分析,其实就是把C语言代码转换成汇编能理解的代码。

符号汇总就比较特殊,这里拿出细讲:

符号汇总,其实就是 把一个文件中那些全局的符号汇总到一起 

比如:

想听听 .c 到 .exe 的故事吗?

红色方块部分是汇总出的符号

 某种意义上,汇总的符号是函数的符号。那这有什么用呢?先不急,接着看。

2.2.3 汇编

进一步,令编译后的文件只进行汇编操作

输入指令: gcc test.s -c 

想听听 .c 到 .exe 的故事吗?

又出现了新的 test.o 文件 

打开 test.o :

想听听 .c 到 .exe 的故事吗?

嗯,看不懂。

其实是 二进制 啦 ~ 当然看不懂

那么 汇编的操作 也显而易见了:

• 把汇编指令翻译成二进制指令 

其中又有一个重要的过程: 形成符号表 

我们说了看不懂,那谁能帮我们看懂呢?

在 Linux环境下:

test.o 和可执行文件的格式是 elf ,理应, readelf 可以读

输入命令: readelf test.o -s 

想听听 .c 到 .exe 的故事吗?

这不,汇总的符号就出现了。

想听听 .c 到 .exe 的故事吗?

形成符号表,其实就是把汇总出的符号赋予一个临时地址

2.3 链接

链接链接,通俗来讲就是把多个文件连接起来

链接进行的操作:

• 合并段表

• 符号表的合并和重定位

合并段表解释:

前面提到 .o 文件和可执行文件都是 elf 格式,elf 格式本身具有段,

.o 文件在形成可执行文件的过程中会把相对应的段合并

想听听 .c 到 .exe 的故事吗? 

                                图示

 符号表的合并和重定位:

前面提到了形成符号表,并赋予了一个临时地址,

它们最终会 合并 到一起

对于出现一次的符号来说,地址可以直接用临时赋予的地址;

而对于出现多次的符号来说,地址不统一,就需要 重新定位

想听听 .c 到 .exe 的故事吗?

                                           图示


 

总结:

听了 .c 到 .exe 的故事,是不是感觉 .c 到 .exe 道路有些艰辛呢?

想听听 .c 到 .exe 的故事吗?

一图总结 

 

码文不易 

如果你觉得这篇文章还不错并且对你有帮助,不妨支持一波哦  ????????????