编译与链接
大家都知道编译和链接是一个源程序变为可执行文件必须经历的过程,但现在的IDE具有一键构建的功能,使得编译和链接这两步对于用户来说是不可见的,被封装了起来,例如vs studio中,你只需点击一下build solution,就可以直接得到可执行文件,那么本篇内容就来带大家重新认识编译和链接。
编译
咱废话不多说,先上图:
上图中,左边是我们写的代码,右边是通过编译器生成的机器代码,这个这就是编译的过程。因为一般的文本编辑器是不能显示目标文件(.o)的,所以大家在平时对目标文件的内容不是太了解。
所以编译其实就是把.c/.cpp文件通过编译器翻译成cpu可以直接运行的机器代码,cpu是看不懂你写的那些printf,main函数之类的,都是通过编译器翻译成有图的机器代码,cpu才可以运行。
那我们闲话不多说,举个🌰:
我们写了main.c和add.c两个源代码,然后对其进行编译,如下图:
然后对其进行编译gcc main.c -c gcc add.c -c
(注意,这两句话要分开执行哦,编译源文件一次只能编译一个)。然后就会生成下面两个文件,如图:
这两个.o文件就是编译后的目标文件,到这里其实学过C的小伙伴应该都是知道的,接下来我们讲点小众知识
我们先看下这个目标文件的格式,我们以main.o为例:
首当其冲的是ELF(executable and linkable format),这是linux下可执行文件的通用格式,和windows下的PE是类似的。那为了准确解释ELF,可爱翻了下ELF的技术规范,有3种主要的目标文件类型被称为ELF,请看下图:
那以上就是ELF相关知识,翻译就不翻了。我们来看下ELF这种格式的文件的头部信息,用readelf -h
来显示,如下图:
以上的段表头大小和数目大家可以关注一下,因为后面有个计算题,浅算一下,嘿嘿~
然后我们再看下这个main.o目标文件在内存的情况,如下图:
这里我们解释一下部分段所存储的内容,
-
.text :代码段(存放函数的二进制机器指令)
-
.data :数据段(存已初始化的局部/全局静态变量、未初始化的全局静态变量)
-
.bss :bss段(声明未初始化变量所占大小)
-
.rodata :只读数据段(存放 " " 引住的只读字符串)
-
.comment :注释信息段
-
.node.GUN-stack :堆栈提示段
那除此之外的一些段,后面我会提到,那其他type,address,offset可以暂时不管,就知道一个目标文件在内存中有哪几个段就行。
这里我们来浅算一下这个文件的大小,也就是main.o的大小
首先上图中画红线的两个数据,一个是14个段,还有就是起始偏移量0x360,换算成十进制就是864,那么一共14个段表,每个段表64字节(前面有提到过哦!),那么总的偏移量就是864+14*64=1758字节。那我们来验证一下,是不是这个答案呢
我们用
ls -l
这个命令来查看main.o的大小,如下图:
看到了吧,几乎是一样的,至于为啥差了2字节,可爱也不知道原因,如果有大佬知道可以下方评论写出答案,嘿嘿~
链接
上面讲了关于编译的小知识,现在我们来讲讲链接,这里的链接指的是静态链接。我们先用objdump看一下main.o的一些信息,如下图:
到这里有一个问题,程序在链接时,如何定位到这些需要修改的地址呢?
答:重定向表(relocation table)
我们可以用objdump -r
这个命令来查看main.o中重定向入口,如下图:
那之前我们在说E8后面的跳转地址时说,当main.o链接后,跳转地址就会被修改,那我们对main.o和add.o进行链接,然后查看其跳转地址,如下图:
可以看到地址已经改变了。
那这里我们提一嘴,之前我们提到了main.o的大小,现在我们看看链接后,大小有没有变化,如下图:
是不是很神奇,main.o只要1.8k左右,结果链接一下,变成了17K,是不是感觉离离原上谱,这就是静态链接和动态链接的功劳。也就是我们接下来要讲的东西。
Comments | 1 comment
Blogger Brittani Fenti
Your expressions seamlessly guide my imagination. I automatically picture every aspect you depict.