现在PC平台流行的可执行文件格式主要是windows下的PE和Linux下的ELF,他们都是COFF格式的变种。目标文件就是源代码编译后单位进行链接的那些中间文件,它跟可执行文件的内容与结构很相似,所以一般跟可执行文件格式一起蚕蛹一种格式存储。从广义上看,目标文件与可执行文件的格式其实几乎是一样的。
不光是可执行文件按照可执行文件格式存储。动态链接库(windows的.dll和linux的.so)及静态链接库(windows的.lib和linux的.a)文件都按照可执行文件格式存储。
目标文件中的内容至少有编译后的机器指令代码,数据。除了这些内容,目标文件中还包括了连接时所需要的一些信息,比如符号表,调试信息,字符串等。一般目标文件将这些信息按不同的属性,以 Section 的形式存储,有时候也叫 Segment , 在一般情况下,它们都表示一个一定长度的区域,基本上不加以区别,唯一区别是在ELF的链接和装载视图的时候。
程序源代码编译后的机器指令经常放在代码段里,代码段常见的名字有".code’ 或 ‘.text’ ,全局变量和局部静态变量数据经常放在数据段’.data’ 里面. 未初始化的全局变量和局部静态表里保存在一个叫’.bbs’的段里。
总体来说,程序源代码被编译后主要分成两种端:程序指令和程序数据。代码段属于程序指令,而数据段和.bss 段属于程序数据。
链接的本质就是把多个不同的目标文件之间相互粘到一起。为了使不同目标文件之间能够相互粘合,这些目标文件之间必须有固定的规则。在连接中,目标文件之间的相互平和实际上是目标文件之间对地址的应用,即对函数和变量的地址的引用。每个函数和变量必须要有自己独特的名字,才能避免链接过程中不同变量和函数之间混淆。在链接中,我们将函数和变量统称为符号(Symbol),函数名和变量名就是符号名。
每一个目标文件都会有一个相应的符号表(Symbol Table),这个表里面记录了目标文件中所用到的所有符号。每个定义的富豪有一个对应的值,叫做符号值(Symbol Value),对于变量和函数来说,符号值就是它们的地址。
多个源文件链接在一起时,会出现符号名重复的情况,gcc 会把名称前面加上下划线来处理。但是这样还是有可能出现重复。于是就有了函数签名。像java,c++中都有函数签名,不同的重载方法的符号名最终会由 返回值+函数名+形参 组成。总之最后链接的时候,gcc会让相同的函数名或者相同名称的变量名不一样。
C ++ 为了与C兼容,在符号的管理上,C++有一个用来声明或定义一个C的符号的 “extern C” 关键字用法:
extern "C"{
int func(int);
int car;
}
C++ 编译器会将在extern C 的大括号内部的代码当做C语言代码处理。所以上面的代码中,C++名称修饰机制将不会起作用。所以在c++的编译环境中,如果用到了C 里面的类库函数,必须用 extern 关键字包裹起来,不然C++ 的链接器会按照C++ 的 符号名去命名对应的函数签名,自然也就找不到对应的C 语言中的函数签名了。