C语言程序的编译和链接

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

在ANSI C(标准C)的任何一种实现中,存在两个不同的环境

翻译环境:在这个环境中源代码被转换为可执行的机器指令(二进制代码)。

执行环境:它用于实际执行代码。

补充:计算机是能够执行二进制指令的,但是我们平常写出的C语言代码是文本类信息,计算机不能直接理解。所以需要翻译环境将C语言的代码转换为二进制指令(可执行程序),再通过执行环境执行可执行程序。

2.翻译环境的组成

翻译环境包括程序的“编译”和“链接”过程

比如:我们创建了一个程序,程序里有三个源文件,分别是:test.c        Add.c 以及 Sub.c

这三个源文件分别单独经过编译器处理生成相应的目标文件(.obj)的过程,称之为:编译

当我们有了多个目标文件之后,这些目标文件加上链接库一起经过链接器的处理生成可执行程序(.exe)的过程,称之为:链接

这里大家可能会比较陌生的应该是链接库。

链接库:大家平常应该都会使用一些库函数例如:scanf,printf等等,我们为什么能够直接使用这些库函数呢?

其实这些库函数平时是放在静态库当中的

我们在MSDN当中可以搜索一下scanf这个库函数,可以看到下面有个叫 Libraries 的东西,Libraries 的意思就是库的意思,里面后缀为 .LIB 的文件就是静态库。

如果我们在程序中使用了scanf这个函数,像VS这样的集成开发环境就会自动把 scanf 的静态库以及你所使用的源文件所生成的目标文件,通过链接器的链接生成可执行程序。

补充:像 VS 2019 这样的集成开发环境集成了 编译器:cl.exe   链接器:link.exe 以及 调试器

我们在电脑中其实是可以找到的。

下面我们写一个简单的代码来观察一下目标文件和可执行程序。

这里我创建了两个源文件,当编译链接这两个文件之后,我们打开文件所在的目录就可以观察到相应的目标文件以及可执行程序。

我们首先打开源文件所在的文件夹,之后在文件夹里找到x64文件并打开,里面会有个Debug文件,我们的目标文件以及可执行程序就在里面。

3.编译的详解

3.1编译过程的概括

上述过程只是对编译链接过程做了一个概括,下面来具体的讲解编译过程。

整个编译环境可以分为:编译和链接。

编译过程又可以细分为三个部分:预编译(预处理)、编译 以及 汇编。

补充:在 VS 2019 这样的集成开发环境中,不方便观察编译的具体细节,所以之后的代码演示,我在 VS Code 上使用 gcc 编译器进行操作。

有关VS Code 如何操作使用的具体细节,可以参考 比特鹏哥的视频: 1. VSCode是什么?_哔哩哔哩_bilibili

3.2预编译(预处理):

预处理 选项 gcc - E test.c - o test.i
                预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。

输出之后的结果如图所示:

我们可以看到,下面终端的指令输入,以及右边的test.i的文件里有很长的内容,大概八九百行,但其实前面绝大部分的内容是对#inlude 这个头文件的包含,我们只需要观察蓝色方框里的内容就好。

通过对比 test.c 文件和 test.i 文件的内容我们不难发现:

原本在 test.c 中的注释信息消失了:这就是注释的删除操作。

在 test.c 中的#include <stdio.h>头文件也被包含进来了,上文有提到:这就是头文件的包含操作。

在 test,c 中 #define 定义的 M 在文本中直接被替换成了 100:这就是符号替换操作。

这些操作都是预编译(预处理)阶段,编译器需要操作的事情。

3.3编译:

 编译 选项 gcc - S test.c
                  gcc -S test.i
             编译完成之后就停下来,结果保存在test.s中。

输出结果如下:

我们可以发现生成的 test.s 文件里面是我们看不懂的内容,其实这就是汇编代码。

编译过程不仅仅是把 C语言的代码转换为汇编代码。

它还进行了 语法分析、词法分析、语义分析、以及符号汇总等操作,这些过程我就不再解释,如果大家是计算机专业的学生,会在《编译原理》这门课中学到,如果不是,可以自行了解。

3.4汇编:

汇编选项:gcc -c test.s  汇编完成之后停下来,结果保存到 test.o 中.

输入指令后,结果如下:

因为是二进制文件,所以并不支持查看。

汇编阶段进行的操作就是:将汇编指令翻译成二进制指令和形成符号表操作。

4.链接

运行结果如下:

链接生成的.exe可执行程序也是不可查看的。

链接过程的主要操作:合并段表

                                     符号表的合并和重定位

通过上述分析,我们发现编译过程进行了符号汇总操作,汇编过程进行了形成符号表操作。

那么它们有什么用呢?其实,作用是为了在链接阶段查看这个符号表,并进行符号表的合并和重定位。

很多人看到这里可能不太理解什么是 符号汇总 ,什么是 形成符号表,什么是合并段表 以及什么是 符号表的合并和重定位。

下面我们来重新写一段代码来讲解。

我们对这个文件进行预编译,编译以及汇编操作:重新生成了一个 test.o 文件

如果我们硬把它打开,其实里面看到的是乱码。但实际上 目标文件(.o)是有自己的格式的。

补充:Linux 环境下 gcc 编译产生的 目标文件(.o),可执行程序(.exe)都是按照ELF这种格式来存储的。

我们使用 readelf 工具就能够识别elf这种格式的文件。

指令:readelf test.o

结果如下:

我们输入 readelf test.o -a 结果如下:

我们可以看到里面的内容是按照不同的段来存储的,类似下图。

我们在里面可以找到一个段,是存储符号的。

我们在里面可以看到一些 全局的符号 。

我们在编译的时候就开始进行符号汇总了,那么到底是如何来汇总的呢?

如下图所示:

经过上述图示:我们知道了符号表的相关操作,所以它到底有什么用呢?

当我们链接形成一个可执行程序时,如果我们要找Add函数,通过符号表中的地址就能找到函数,如果找不到(函数的名字写错等),或者通过一个无效的地址(声明出现的无效地址)去找,程序就会报错。

补充:上述的讲解还是比较“粗略”,如果有兴趣可以看一看:《程序员的自我修养》里面对编译,链接的过程进行了更详细的讲解。

  • 32
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值