欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

编译链接(预编译,编译,汇编,链接)

程序员文章站 2023-12-25 11:23:03
...

编译链接

一个程序要变成一个进程需要将指令和数据加载进内存中就可以了,而在变成进程的过程需要五步来完成

所有的操作系统只识别机器码,也就是0,1码,即二进制
我们所学的C/C++等语言都属于高级语言
越接近机器语言的就是低级语言比如汇编语言
在高级语言转换成机器码的过程中,并不是直接变成机器语言,而是先变成汇编语言,在变成机器码

一、预编译(生成.i文件)

1.、删除#define并作文本替换
2.、处理#if,#endif等预编译指令
3.、处理#include等头文件 递归展开的过程
4.、删除注释
5.、添加行号和文件标识
6.、保留#pragma指令

二、编译(生成.s文件)

1.、词法分析
2.、语法分析
3.、语义分析
4.、代码优化
5.、生成指令(生成汇编代码)

三、汇编(生成.o文件)

1.、翻译指令(将汇编代码翻译成机器码,每一个汇编语句几乎都对应一条机器指令,但依旧无法运行)

补充:

.o文件或者叫目标文件,或者叫可重入的二进制文件
我们用一个例子a.c来讲解.o文件的布局

我们先来看看a.c的例子
让我们先来区分一下那些是指令那些是数据
在图中
画红线的是.data :放已初始化但初始化不为0的数据
画蓝线的是.bss :放未初始化或者初始化为0的数据
画黑线的是.text :存放指令

编译链接(预编译,编译,汇编,链接)

在linux中看目标文件内容用下面的命令来查看

objdump -h 目标文件名

编译链接(预编译,编译,汇编,链接)
从上图给出的信息并不能看到0x0000 0000 ---- 0x0000 0034这段的信息,我们如果想要查看这段的信息
用linux的如下命令来查看

readelf -h 目标文件名

编译链接(预编译,编译,汇编,链接)
从上面查看目标文件(.o文件)的内容我们就可以大致得出目标文件的布局
编译链接(预编译,编译,汇编,链接)

这会出现一个问题,为什么没有出现.bss?

根据段的起始偏移和段大小计算一下可以得到.bss不占用目标文件的空间。既然.bss段没有占目标文件的空间,编译器怎么能打印出.bss段的大小呢?

从上上个图我们可以得出这是ELF Header中的信息, ELF Header中有一个叫做section headers的段,这个段保存了目标文件中每个段的详细信息,包括段的大小,起始偏移等等信息。编译器只要访问这个段就可以知道每个段的详细信息。.bss虽然不占空间但是.bss段的详细信息都被保存在section headers中了。
同时大家可以看到这个Entry point address的值是0,还没有给出具体的main函数入口地址

另外,从上上个图中我们可以看出.bss大大小是0x0000 0014也就是20字节,但从程序上我们可以得出.bss区的数据一共有6个,每个占四字节,理应对应24字节,至此我们又有两个问题??

为什么少四个字节?以及,少的是哪个变量?

要解决这个问题我们就得说一下C语言里的强弱符号
我们知道存放在.data和.bss段的变量都要有一个名称来标识这个变量,这个名称称为符号

强弱符号

强符号:全局的已初始化的符号
弱符号:全局的未初始化的符号

强弱符号的选取规则:
1.如果在一个工程中出现同名的强符号,编译一定出错
2.如果出现同名的一个强符号一个弱符号,则不会报错,且使用的时候优先使用强符号
3.如果出现了多个同名的弱符号,则优先使用占用内存较大的符号

大家都知道一个项目可能有多个源文件。编译阶段都是每个文件单独编译的。可能在其他文件中存在强符号,所以没办法在编译期间确定具体的符号。因此将本文件的弱符号存放在COM块,而不是.bss段。

我们可以通过下面的linux指令来产看

objdump -t 目标文件名

编译链接(预编译,编译,汇编,链接)

很显然,这里的a3就是个弱符号,所以a3应该在COM里,导致了上面的问题

四、链接(生成.exe文件,也叫可执行文件)

1.地址和空间分配
2.符号解析
3.符号重定位

补充:

链接后,对每个符号给出了具体的虚拟地址。也已经清楚了每个符号的强弱,所以a3存放在.bss段了。链接完了以后就可以确定a3这个弱符号的符号选择了。对于.o文件中引用的外部符号也确定了具体的定义位置,
把这两个外部符号放到对应的段中。
符号解析和符号重定位就发生在这个过程中。

符号解析:
链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来,即使用的符号一定要找到相应的定义。可分为局部符号解析和全局符号解析。

局部符号解析:引用定义在相同模块中的局部符号的引用,符号解析非常的简单明了,就不用介绍了。
全局符号解析:当编译器遇到一个不是当前模块中定义的符号时,会假设该符号时在其他某个模块中定义的,生成一个连接器符号表条目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个符号的定义,就输出一条错误信息并终止。

全局符号解析还因为多个目标文件可能会定义相同的名字的全局符号。在这种情况下,链接器必须要么标志一个错误,要么以某种方法选出一个定义并抛弃其它定义。

符号重定位
对.o文件中.text段指令中的无效地址给出具体的虚拟地址或者相对位移偏移量。

在一个项目中可能会有很多源文件,可能某个源文件需要调用其他源文件文件里的函数,即源文件引用外部变量或者函数,在汇编阶段后,编译器给这个源文件里的外部引用的变量或者函数的入口地址都是无效地址,而通过链接的符号重定向就可以使之前的无效地址变成具体的虚拟地址

链接完成之后再来看看可执行文件的头部信息
编译链接(预编译,编译,汇编,链接)

可以看到这个时候这个程序的入口地址已经给定0x80482e0这个就是main函数的入口地址。现在程序就可以运行了。

五、运行

相关标签: linux 编程语言

上一篇:

下一篇: