您的位置:首页 > 职场人生

程序员的自我修养-----第2部分静态链接总结

2019-04-03 01:03 190 查看

一、编译和链接

编译的四个步骤:
1、预处理
2、编译
3、汇编
4、链接

预编译
预编译过程主要是处理那些源代码文件中的以 “#” 开始的预编译指令,主要规则如下:
(1)将有所有的 “#define"删除,并且展开所有的宏定义。
(2)处理所有条件预编译指令,比如:”#if"、"#ifdef"、"#elif"、"#endif"、"#else"。
(3)处理 “#include” 预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件,且要避免头文件重定义的问题。
(4)删除所有的注释 “//” 和 “/**/”。
(5)添加行号和文件标识,比如:#2 “hello.c” 2 ,以便于编译时编译器产生调试的行号信息及用于编译时产生的编译错误或警告时显示行号。
(6)保留所有的 “#pragma” 编译器指令,因为百编译器须要用到它们。如内存对齐时

编译
编译过程就是把预处理完的文件进行扫描(词法分析)、语法分析、语义分析、源代码优化。代码生成和目标代码的优化。

1、词法分析
首先将源代码程序输入到扫描器,进行词法分析,将将源代码的字符分割成一系列的记号(Token)。
记号一般可以分为:关键字、标识符、字面量(包括数字、字符串)和特殊符号(如:加号、等号)。同时将标识符存放到符号表,将数字字符串常量存放到文字表
2、语法分析:
将记号进行语法分析,产生语法树(以表达式为结点的树通常以数字和符号为结点)同时确定运算符的含义和优先级。如果表达式不合法,编译器将会报出语法分析的错误
3、语义分析
编译器所能分析的语义只能是静态语义,所谓静态语义就是在编译期就能确定的语义,通常为声明和类型的匹配,类型的转换。对应的动态语义就是在运行的时候才能确定的语义(比如0作为除数)。
经过语义分析阶段以后哦,整个语法树的表达式都要标识了类型,如果有些类型需要做隐式转换,语义分析程序会在语法树中插入相应的转换节点。
4、中间代码的生成
源代码优化器将会把整个语法树转换为中间代码,它是语法树的顺序表示,其实已经很接近目标代码。它一般跟目标机器和运行环境无关。中间代码的类型有三地址码和P-代码。
中间代码使得编译器分为前端和后端,前端负责产生和机器无关的中间代码,编译器后端将中间代码转换为目标机器代码。这样对于一些可以跨平台的编译器而言,他们可以针对不同的平台使用同一个前端和针对不同机器平台的数个后端。
5、目标代码生成和优化
编译器后端主要包括代码生成器和目标代码优化器,将中间代码通过代码生成器转化为目标机器代码,优化是通过目标代码优化器,选择合适的寻址方式,将乘法转换为位移,删除多余的指令。

链接
主要包括了地址和空间的分配,符号决议和重定位
重定位:当多个文件一起链接成一个可执行程序的时候,因为编译器是单文件有效哦,为了找到其他文件中的函数或者是变量会进行地址的修正,是当前文件可以找到引用其他文件的变量或函数地址,从而进行使用。

二、 目标文件里有什么

目标文件的格式:
现在的PC流行的可执行格式主要适合Window下的PE(Protable Executable)和linux的ELF(Executable Linkable Format),它们都是COFF(Common file format)格式的变种。从广义上来看,目标文件和可执行文件的格式其实几乎是一样的,所以在Windows下,统称他们为PE-COFF文件格式,Linux统称为ELF文件。动态链接库(windows上.dll 和 Linux上的.so)和 静态链接库(windows上的.lib 和 linux的.a文件)。目标文件除了编译后的机器指令代码和数据之外,海包括一些符号表、调试信息、字符串等。一般目标文件将这些信息按不同的属性,以“节”(Section)的形式存储,有时也叫“段”(Segment)
*一般C语言得到编译后的执行语句都编译成了机器代码,保存在.test段,已初始化的全局变量和局部静态变量都保存在.data段;未初始化的全局变量和局部静态变量一般都放在.bss段。*未初始化的全局变量和局部静态变量默认值都为0,但是因为它们是0,在.data段分配空间并存放数据0是没有必要的。程序运行是它们的确斗都必须要占用空间,并且可执行文件必须记录所有未初始化的全局变量和局部静态变量的大小的总和,记为.bss段,所以.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以它在文件中不占用空间,但会占用程序的内存空间

ELF文件主要是.data(数据段)、.bss、.rodata、.text、.comment(注释信息段)
数据段和只读数据段
.data段:保存的是一些已经初始化的全局边量金额局部静态变量。
.rodata段:保存的一般是程序里面只读的数据,如字符串,const修饰的变量。
.bss段存放的是未初始化的全局变量和局部静态变量,其实我们可以从符号表上看到,只有未初始化的局部静态变量存储在.bss段,而为初始化的全局变量则没有存放在任何段,只是未定义一个"COMMON"符号。这是由于有强符号和弱符号的原因。
.common段:存放的是编译器的版本信息

ELF文件头,包含了描述整个文件的基本属性,如ELF文件版本、目标机器型号、程序入口的地址等。紧接着就是段表,描述了ELF文件中的与段有关的重要结构就是段表,包含了ELF文件包含所有段的信息,比如段名、段的长度、文件中的偏移等
文件头结构"Elf32_Ehdr":
typedef struct
{
unsiged char e_ident[16];
Elf32_Half e_type;//文件类型
Elf32_Half e_machine;//平台
Elf32_Word e_version;//版本号
Elf32_Addr e_entry;//程序入口
Elf32_Off e_phoff;
Elf32_Pff e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phum;
Elf32_Half e_shentsize;//段表描述符大小
Elf32_Half e_shnum;//段表描述符数量
Elf32_half e_shstrndx;//段表字符串表所在的段在段表中的下标

}Elf32_Ehdr;

重定位表:
链接器在处理目标文件时,需要对目标文件中的谋些部分进行重定位,即代码段和数据段中那些对绝对地址的引用的位置。这些重定位的信息都记录在ELF的重定位表上,对于需要重定位的代码段或数据段,都会有一个相应的重定位表。

字符串表
ELF文件中用到了很多字符串,比如:段名、变量名等。因为字符串的长度往往是不定的,所以用固定的结构来表示它比较困难。一种很常见的做法是把字符串集中起来存放在一个表中,然后使用字符串在表中的偏移来引用字符串。

所有段的位置大致如下:

链接的接口------符号

在链接中,目标文件之间的相互拼合实际上就是目标文件之间对地址的引用,即对函数和变量地址的引用。我们把函数和变量统称为符号,函数名或变量名就是符号名。每一个目标 1c6f4 文件都有一个相应的符号表,这个表里面记录了目标文件里面的所有的符号,每个符号都有相应的值,其值就是它们的地址。符号可以分为以下几种类型:
(1)全局符号:定义在本目标文件的全局符号,可以被其他目标文件引用。
(2)外部符号:在本目标文件引用的全局符号,却没有在本目标文件定义。
(3)段命:这种符号往往由编译器产生,它的值就是该段的起始地址。
(4)局部符号,这类符号只在编译单元内部可见。
(5)行号信息:即本目标文件指令与源代码行对应关系。它是可选的。

函数签名:包含了一个函数的信息,包括函数名,它的参数类型。它所在的类和名称及其他信息。函数签名用于识别不同的函数就像签名用于是被不同的人一样,函数名字只是函数签名的一部分。编译器和链接器在处理符号名时,它们使用某种名称修饰的方法使得每个函数签名对应一个修饰后的名称。编译器在将C++源代码编译成目标文件时,会将函数和变量的名字进行修饰形成符号名。C++编译器和链接器都使用符号来识别和处理函数和变量,所以对于不同的函数签名的函数,即使函数名相同,编译器和链接器都会认为是不同的函数。

extern “C”
为了和C兼容,在符号管理上,C++有一个用来声明或定义一个C的符号的方法 extern "C"关键字的用法,里面的代码不会按照C++的名称修饰机制来生成符号。

强符号和弱符号
我们经常在编程中碰到一种情况叫做符号重定义。多个目标文件中含有相同的名字全局符号的定义,那么这些目标文件链接时会出现符号重定义的错误。
C/C++编译器会将一个初始化的全局变量和函数定义为一个强符号,未初始化的全局变量为弱符号。
强、弱符号的处理:
(1)不允许重定义多个强符号(即不同的目标文件不能有名字相同的强符号);
(2)如果一个符号在一个文件里是强符号,而在其他文件里都是弱符号,则按强符号来处理。
(3)同是弱符号,取的是所占空间最大的那个
强引用和弱引用:
目前我们所看到的对外部目标文件的符号引用在目标文件被最终链接成可执行文件时,它们须要被正确决议,如果没有找到该符号的定义,链接器将会报符号未定义的错。这种成为强引用。与之对应的是弱引用,在处理弱引用的时候,如果该符号有定义,则链接器会引用决议;如果没有定义,则链接器对于该引用不会报错。链接器处理强引用和弱引用的过程几乎是一样的。但对于未定义的弱引用,链接器不会报错,它会默认给它一个值可能是0,也可能是一些特殊值。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: