编译链接过程
一、从程序源代码到最终可执行文件的四步骤
预编译(.i)---》编译(.s)---》汇编(.o)---》链接(.exe)
二、编译链接过程
1. 预编译
(1)#define:将所有的“#define”删除,并且展开所有的宏定义
(2)#incldue:处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置,此过程是递归进行的,因为被包含文件中还可能包含其他文件,即展开声明
(3)处理所有条件预编译命令,例如:# if........#end if 语句
(4)添加行号和文件标识名(以便编译时编译器产生调试用的行号以及产生错误时显示)
(5)删除注释
(6)保留所有#pragma编译器指令 :由于宏定义的展开和头文件的插入,可以通过预编译后的文件来查看是否有宏定义或者头文件包含是否有问题
2. 编译
(1)词法、语法、语义的分析
(2)代码的优化
3. 汇编
(1)将代码指令转化转化成机器语言(二进制文件)
补充:在汇编中的未知的地址都是通过用偏移量来表示的,未知的值是0
4. 链接
(1)合并段和符号表
(2)确定段大小和起始偏移
(3)符号解析
(4)分配地址空间
(5)符号重定位(发生在指令段)
5. 运行
(1)创建PCB------映射--------》内存
(2)只加载指令和数据
三、目标文件
1. 定义:编译器编译源代码后生成的文件。
(1)从结构上:属于已经编译后的可执行文件格式,只是还没有经过链接过程,只是内部有些符号和地址没有改变
(2)本身是按照可执行文件格式存储的,只是在结构和可执行文件有所差别
(3)Windows : PE
Linux : ELF
2. 格式
(1) ELF HEADER:文件头,描述整个文件的文件属性、包括文件是否可执行、是静态链接还是动态链接以及函数入口地址等,文件头还包括一个段表:是一个描述文件各个段的数组
(2) .text : 编译器编译后执行语句都编译成机器代码即保存到此段
(3) .data: 已经初始化的全局变量和局部静态变量且值不为0的数据
(4) .bss: 未初始化的全局变量和局部静态变量且值为0的数据(只是为未初始化的全局变量和静态变量预留位置而已,不占内存空间)
补充:初始化为0的数据不放在.data段的原因:未初始化的全局变量和局部静态变量默认值都为0,在.data段分配空间并存放数据0是没有必要的。
3. 链接的接口--------符号
(1)在链接中将函数和变量统称为符号,函数名或者变量名成为符号名,每个目标文件中都会有一个相应的符号表(记录所有用到的符号以及对应的符号值,符号值也就是函数和变量相对应的地址)
(2)符号的修饰
由于规则不同, C/C++的函数名和变量名生成的符号不同。
为了使得C和C++兼容,C++中有一个用来声明或者定义C的符号关键字extern”C”
extern C
{
//C++编译器将大括号中内部中的代码当做C语言代码处理
}
(3)强符号和弱符号
强符号:C/C++中编译器默认函数和初始化了的全局变量为强符号
弱符号:未初始化的全局变量为弱符号
规则:
a. 不允许强符号被多次定义
b. 一个符号在一个文件中若是强符号,在其他文件中都是弱符号
四、链接的过程
1. 分配地址空间
2. 符号解析:找到所有UNDEFINE的,即在符号引用的地方找到符号定义的地方
处理的是外部符号(没有属性的符号)
补充:链接器只关心全局符号
3. 符号重定位:修改.test中的未知函数地址和变量地址