目标文件

目标文件是指:编译器编译源代码后生成的文件,那么目标文件里面到底存放的是什么呢?或者说我们的源代码在经过编译以后是怎么样存储的呢?

目标文件从结构上将,它是已经编译后的可执行文件格式,只是好没有经过链接的过程,其中可能有些符号或有些地址还没有被调整。其实,目标文件本身就是按照可执行文件格式存储的,只是跟真正的可执行文件在结构上稍有不同。

可执行文件格式涵盖了程序的编译、链接、装载和执行的各个方面。了解可执行文件的格式对认识系统,了解集成编译器背后的运行机理,还是很有好处的。

1.目标文件的格式是什么样的?

现在PC平台流行的可执行文件格式(Executable),主要是Windows下的PE(Portable Executable)和linux的ELF (Executable Linkable Format),他们都是COFF(Common File Format)格式的变种。目标文件就是源代码编译后为进行链接的那些中间文件(Windows下面为.obj文件;Linux下面为.o文件),它和可执行文件的内容和结构很相似,所以一般和可执行文件采用同一种格式进行存储。从广义上来讲,目标文件与可执行文件的格式其实几乎是一模一样的,所以,我们可以广义的将目标文件和可执行文件看成是同一种类型的文件。在Windows下,我们把目标文件和可执行文件都统一称为PE-COFF文件,在Linux下,我们把它们统称为ELF文件。
当然,事情没有这么简单!不光是可执行文件(Windows下面的.exe和Linux下面的ELF文件)按照可执行文件格式存储。动态链接库(DLL,dynamic linking library)[Windows下面的.dll文件和Linux下面的.a文件]以及静态链接库(Static linking Library)[Windows下面的.lib文件和Linux下面的.a文件]都是按照可执行文件格式存储的。只不过,在Windows平台下,他们按照PE-COFF格式存储,而在Linux平台下按照ELF格式进行储存。
ELF文件标准里面把系统中采用ELF格式的文件归为以下四类:

ELF文件类型说明实例
可重定位文件
Relocatable File
这类文件包含了代码和数据,可以被用来链接成可执行文件
或共享目标文件,静态链接库也归属于这一类
Linux的.o文件
Windows下的.obj文件
可执行文件
Executable File
这类文件包含了可以直接执行的程序,它的代表就是ELF文
件,他们一般都没有扩展名
比如bin/bash文件
Windows的.exe文件
共享目标文件
shared Object File
这种文件包含了代码和数据,可以在以下两种情况下使用,一
种是链接器可以直接使用这种文件跟其他的可重定位文件和共享
目标文件链接,产生新的目标文件。第二种是动态链接器
可以将几个这样的共享目标文件与可执行文件结合,
作为进程映射的一部分来运行。
Linux的.so文件
Windows下面的.dll文件
核心转储文件
Core Dump File
当进程意外终止时,系统可以将该进程的地址空间的
内容及终止时的一些其他信息转储到核心转储文件中
Linux下面的core dump

2.目标文件与可执行文件格式的小历史

目标文件与可执行文件格式与操作系统和编译器密切相关,所以不同的系统平台下会有不同的格式,但这些格式又大同小异,目标文件格式与可执行文件格式的历史几乎就是操作系统的发展史。
COFF是由Unix System VRelease 3首先提出并且使用的文件规范,后来微软公司基于COFF格式,制定了PE格式标准,并将其用于当时的Windows NT系统。System VRelease 4在COFF的基础上引入了ELF格式,目前流行的Linux系统也是以ELF作为基本的可执行文件格式。这也能解释为什么目前PE和ELF如此相似的主要原因,因为他们都是来源于同一种可执行文件格式COFF。

3.目标文件到底是什么样的?

猜也可以猜到,目标文件中的内容至少有编译后的机器指令代码、数据。没错,除了这些内容以外,目标文件中还包含了链接时所需要的一些信息,比如符号表、调试信息、字符串等。一般目标文件将这些信息按不同的属性,以“节”(section)的形式存储,有时候也叫“段”(segment),在一般情况下,他们都表示一个一定长度的区域,基本上不加以区别,唯一的区别是在ELF的链接视图和装载视图的时候,需要特别注意。
程序源代码编译后的机器指令经常放在代码段(Code Section)里,代码段常见的字有“.code”和“.text”;全局变量和局部变量数据经常放在数据段(Data Section),数据段的一段名字都叫“.data”。下面是一个简单的程序被编译成目标后的结构,如下图所示:


假设上图的可执行文件格式是ELF,从图中可以看到,ELF文件的开头是一个“文件头”,他描述了整个文件的文件属性,包括文件是否可执行、是静态链接还是动态链接以及入口地址(如果是可执行文件)、目标硬件、目标操作系统等信息。头文件包含一个段表(Section Table),段表事实是一个描述文件中各个段的数组。段表描述了文件中各个段在文件中的偏移位置及段的属性,从段表里面可以得到每个段的所有信息。文件头后面就是各个段的内容,比如代码段保存的就是程序的指令,数据段里面保存的就是程序的静态变量等。
从上图我们也能看出,
一般C语言的编译后执行语句都编译成机器代码,保存在.text段;
已初始化的全局变量和局部静态变量都保存在.data段;
未初始化的全局变量和局部静态变量一般放在一个叫 .bss 的段里。
我们知道未初始化的全局变量和局部静态变量默认值都为零,本来他们也可以被放在.data段里的,但是因为他们都是0,所以为他们在.data段里分配空间并且存放数据0是没有必要的。程序运行的时候他们的确是要占用内存空间的,并且可执行文件必须记录所有未初始化的全局变量和局部静态变量的大小总和,记为“.bss”段。所以,.bss段只是为未初始化的全局变量和局部静态变量预留位置而已。他并没有内容,所以他在文件中也不占据空间

4.程序的指令和数据为什么要分开存放?

总体来说,程序源代码被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。
这样,我们可能就会产生疑问:为什么要这样麻烦,把程序的指令和数据的存储分开?混杂的放在一个段里面岂不是会更简单?其实数据和指令分段的好处有很多,主要如下:
#1:当程序被装载后,数据和指令分别被映射到两个虚存区域。由于数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚存区域的权限可以被分别设置成可读写或只读。这样可以防止程序的指令被有意或无意地改写
#2:对现在的CPU来说,他们有着极为强大的缓存(Cache)体系。由于缓存在现代的计算机中地位非常重要,所以程序必须尽量提高缓存的命中率。指令区和数据区的分离有利于提高程序的局限性。现在CPU的缓存一般都被设计成数据缓存和指令缓存分离,所以程序的指令和数据被分开存放对CPU的缓存命中率提高有好处。
#3:这是最重要的原因!就是当系统中运行着多个改程序的副本时,他们的指令都是一样的,所以内存中只需保存一份该程序的指令部分。对于指令这种制度的区域来说是这样的,对于其他的只读数据也是这样的。比如,很多程序里面带有的图标、图片、文本等资源也是属于可以共享的。当然每个副本进程的数据区域是不一样的,他们是进程私有的。在动态链接的系统中,该种方式可以节省大量内存。比如我们常用的Windows Internet Explorer 7.0 运行起来以后,他的总虚存空间为112844KB,他的私有数据是15944KB,既有96900KB的空间是共享部分!!!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值