本期介绍🍖
主要介绍:代码是如何一步步的转化成可执行城西的,详细介绍了编译和链接的过程,特别是在编译还可分为预编译、编译、汇编三个阶段,介绍每个阶段分别干什么。👀。
文章目录
- 一、概述🍖
- 二、编译与链接的过程🍖
- 2.1 预编译 / 预处理🍖
- 2.2 编译🍖
- 2.3 汇编🍖
- 2.4 链接🍖
- 三、程序执行时的过程🍖
- 四、总结🍖
一、概述🍖
在ANSIC规定任何一个C语言代码都会经过下面两个环境:
- 翻译环境:将源代码转换成可执行的机器指令。
- 执行环境:实际执行代码。
而今天主要讲述的是翻译环境,也就是编译和链接这两个部分。想必大家在学习《C语言》第一章时就听过这样子的一个说法:一个程序必然需要经过编译和链接这两个阶段才能生成一个可执行的代码,计算机才能执行。书上给出的原因是:我们所编写的原代码(也就是以.c
为后缀的文件)计算机是看不懂的,它只能读懂二进制的代码。所以必须经过人为的翻译,使其转换成计算机可以读懂的代码。事实也确实如此,不过我还需要补充几点:如下图所示。
- 每一个源文件(.c)都会单独经过编译器操作生成一个与之对应的目标文件(.obj)
那有人就要这么问了:为什么一个项目中会存在多个源文件? 那是因为,当项目组在开发一个软件的时候,是不可能只存在一个源文件,不然大家怎么协同啊,你写完我写,我写完再他写,是不是太浪费时间了呀。所以在一个工程当中大家是分模块写的(也就是.c文件),你写你的模块,我写我的模块,最后再把所有模块合并成完整的项目。
下面我们来证明一些是否真是每一个源文件都会生成自己所对应的目标文件。
在该项目的文件路径底下,当调试过后test.c
和Add.c
会分别生成test.obj
和Add.obj
这两个目标文件。
- 链接器会把所有目标文件加上链接库一起合并生成一个可执行的程序(.exe的文件)
注意:在VS中使用的编译器是cl.exe
,而链接器是link.exe
的工具。
二、编译与链接的过程🍖
通过上面的学习我们知道一个源程序想要转换成可执行程序,就必须经过编译和链接两个过程。但编译这个阶段其实没有想象中的这么简单,它还可以细分为三个阶段:预编译(预处理)、编译、汇编。如下图所示:
至于每一个阶段都干了些上面这是我们今天重点想要论述的。而由于VS是集成开发环境(也就是说,这种开发环境省略了按步进行调试,是直接一步到位的,中间过程我们无法获取),是无法观测到编译期间的每一步细节的,所以后面我们会使用gcc编译器来进行演示。
2.1 预编译 / 预处理🍖
预编译这个阶段只干三件事:
- 将
#include
涉及的头文件包含到文件中来- 对
#define
所定义的符号进行替换,然后删除定义语句- 删除注释行
可以见得,其实预编译期间只进行了文本操作,譬如头文件的拷贝,字符的替换,语句的删除。注意:预编译期间是不会对源文件进行任何的修改的,它只会对源文件进行相应的处理,然后生成一个新的文件,使之继续进行接下来的编译操作。下面我们来演示证明一下:
可见,预处理阶段会将#include
所涉及的文件整个的包含到我们的文件当中来。
可以看出,预编译后#define MAX 100
与代码中的MAX
进行了替换,然后删除了原定义语句,注释也进行了删除。
2.2 编译🍖
编译阶段笼统的来说:就是把C语言代码转换成汇编代码。不过这个过程较为复杂,需要进行词法分析、语法分析、语义分析、符号汇总,最终才能转换成汇编代码。下面来演示一下,通过对上一个编译生成的test.i
文件进行汇编操作,如下所示:
可以见的,经过编译操作后确实将C语言代码转换成汇编代码。今天重点还需要来讲下 “符号汇总”,因为这一步操作会为后期 “汇编” 和 “链接” 所服务,最终达成某些目的。符号汇总会将代码中所有的全局符号汇总到一起。就譬如上面代码中汇总的全局符号如下图所示:
2.3 汇编🍖
汇编操作大致来说就是把汇编代码转换成二进制代码(计算机可以执行的代码)。下面来演示一下结果:
汇编操作除了会将汇编代码转换成二进制代码,还会形成符号表。所谓的符号表,就是给之前编译期间汇总的全局符号关联一个地址,并制成一张表。下面来举个例子:
至于这张表制出来到底有什么用,你会在链接期间知晓。
2.4 链接🍖
链接期间会做两件事:
- 合并段表
至于什么是合并段表呢?我们需要知道,在gcc中其实后缀为.o
的文件都是以elf的格式来组织文件内容。elf格式的存放习惯会将整个文件分成很多段,每一段都存放不同类型的数据。而链接操作会把多个目标文件加上链接库一起,把相同类型的数据合并到同一段中,最终合并成可执行文件。如下图所示:
注意:此处由多个目标文件和链接库合并成的可执行文件,数据存储的格式也是elf格式。
- 符号表的合并与重定位
符号表的合并与重定位顾名思义,就是将之前每一个目标文件形成的符号表进行合并和删选,最终形成唯一一张表与可执行程序对应。那为什么要形成这样一张符号表呢?为的是能够在链接期间跨文件的寻找函数或者全局变量,同样也是为之后执行代码时能够通过符号表中存放的地址来对函数进行调用。如下所示:
但注意:当Add函数定义不存在时,那么最终合并重定位的符号表中,Add所关联的地址就是无效地址,会在符号表进行审核时,出现链接性错误。如下图所示:
三、程序执行时的过程🍖
- 程序必须载入内存当中才能执行起来。在有操作系统的环境中,一般由操作系统来完成。在独立的环境中,一般是由人工手动载入内存当中的,或者时通过烧录软件将代码置入内存中的。
- 程序执行开始,首先会调用main函数。
- 开始执行程序代码,这时会创建一个临时的调用堆栈,用于存储函数调用和返回时所需的各种参数,以及创建的局部变量。(也就是函数栈帧的创建与销毁)
- 终止程序。
四、总结🍖
这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。