文章目录[隐藏]
- 前言
- 程序的翻译环境和执行环境
- 一. 程序的翻译环境
- 1. 编译
- 1.1 预处理
- 1.1.1 头文件包含
- 1.1.2 注释删除
- 1.1.3 预处理指令的文本替换
- 1.2 翻译
- 1.3 汇编
- 1.1 预处理
- 2. 链接
- 2.1 合并段表
- 2.2 符号表的合并和重定位
- 1. 编译
- 二. 程序的执行环境
- 一. 程序的翻译环境
前言
今天我们要来探究的内容是一个或者多个源文件(.c)是如何变成一个可执行程序(.exe)的,博主将在Linux环境gcc编译器中进行分步演示,让你深入理解程序环境。
程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境。
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境,它用于实际执行代码
我们来简单的看下示意图:
一. 程序的翻译环境
我们通常把一个或多个源文件(.c)
形成一个(.exe)可执行程序
叫做翻译环境,在这个环境中它其实就是将源代码
转换为可执行的机器指令
。
我们来简单看下形成过程,首先我们创建了一个源文件,并没有编译运行这个程序。
接下来我们运行一下这段程序,我们在源文件目录下发现了Debug
文件,点击进入我们看到了.obj
目标文件等一些其他文件:
我们返回上一目录,点击进入Debug
文件在里面我们发现了.exe
可执行程序。
那如果是多个源文件组合在一起,程序运行之后它又会产生几个.obj目标文件
和.exe可执行程序
呢?请看下图例子:
相信大家都知道这两个源文件组合运行起来能得出正确答案,那么它到底生成了几个.obj目标文件
和.exe可执行程序
呢?下面我们一起来观察一下目录。
我们发现目录下出现了两个.obj目标文件
,而只生成了一个可执行程序
。由此,我们是不是能初步的得出一个小结论:每个源文件经过编译过程都会形成各自的.obj目标文件,但.exe可执行程序只有一个。
下面这幅图就是整个翻译环境中的各个过程了:
翻译环境可分为两个过程:
翻译 链接
。这里我们的编译器
执行编译
操作,链接器
执行链接
操作。
- 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
- 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
1. 编译
关于上述的翻译环境我们只是讲了一个大概,并没有进行深入的分析。 编译分为:预处理、翻译、汇编三个阶段。
下面我将带大家在Linux环境gcc
编译器中进行深入的分析每一步的过程,有些读者可能没有学习过Linux环境中的一些命令操作,这没有关系你只要保证自己能听懂就OK。
1.1 预处理
首先我们创建一个test.c
的源文件,它的代码显示如下:
而且当前目录下只有一个test.c
源文件,ls
可以显示到当前目录下有什么文件
接下来输入gcc -E test.c -o test.i
这个指令,它代表的意思是预处理完成之后就停下来,预处理之后产生的结果都放在test.i
文件中。这个文件我们可以随便取名,但是为了编码规范我们写成一般的这种形式,比如什么阶段生成什么后缀的文件名,这里就不做过多的赘述了。
执行完上述指令我们查看到当前目录下出现了test.i
文件。
1.1.1 头文件包含
接下来输入vim test.i
进入test.i
文件查看里面的内容,我们发现里面的代码有800多行,那么这里面放的究竟是什么代码呢?
我们在命令模式下输入G
跳转至文本末尾,我们看到的情况是这样的
接下来我们在命令模式下按下Shift :
,输入内容vs test.c
,此时我们来对比两者一下,你发现什么问题了吗?
我们发现test.i
中头文件不见了,但却出现了大量代码,你觉得是什么原因呢?我们想是不是源文件经过预处理将头文件stdio.h
的内容全部包含进来了吗?下面我们来证明这个事实。
我们输入vim /usr/include/stdio.h
进入到Linux环境中stdio.h
头文件中,我们发现有900多行的代码包含在内
接下来我们对比test.i
与stdio.h
,发现它们两者之间有些内容确实是一样的,但可能由于其他原因我们观察到的可能不是完全一致,这里我们就不刨根挖底了,我们只需知道test.i
里面的这些内容确实就是stdio.h
中的就行了。
从这里我们就可以得出一个结论:预处理会将头文件中的内容拷贝进源文件,#include的本质就是把头文件中相关内容直接拷贝至源文件。
那么我就有一个疑问了,我们的stdio.h
文件中都有900多行的代码,而你的test.i
加上源代码都只有800多行,那么为什么会出现这种情况呢?先把这个问题放一放我们继续分析下面的过程。
1.1.2 注释删除
我将这份代码稍微改动一下,添加几行注释,在test.i
里面观察与test.c
的变化。
此时我们的test.c
文件已经改变,所以接下来我们重新进行gcc -E test.c -o test.i
生成test.i
文件, 我们发现在预处理过后,test.c
里面的注释都被空格替换了。
由此我们得出结论:实际编译器是不关心注释的,所以在预处理阶段是要被删除的;注释只是写给程序员或者其他人员看的,并不参与到程序运算当中。
1.1.3 预处理指令的文本替换
这里以#define
为例,我们将代码再稍加修改一下,通过对比我们发现#define
对文本进行了直接宏替换,并且预处理完之后就消失了。
那么回到上面那个问题,你知道为什么stdio.h
文件的代码行数比test.i
中代码数要多了吗