编译链接总结

2019-12-06  本文已影响0人  angry_zxy

本文是读<程序员的自我修养>的笔记和总结

编译过程

程序的编译可以分解为四个步骤, 预处理->编译->汇编->链接
下面以main.c文件为例分析:

预编译

main.c -> main.i

编译

main.i -> main.s
词法分析: 源代码经过扫描器的处理, 运用类似有限状态机的算法将代码的字符分割成不同的记号
语法分析: 根据不同的记号, 构建语法树
语义分析: 分析语法树的静态语义是否正确, 主要是类型匹配和转换
中间语言生成:将语法树转换成中间代码, 优化一些中间结果. 中间代码使得编译器分为前端和后端, 前端产生与机器无关的中间代码, 后端将中间代码转换成目标机器代码

汇编

main.s -> main.o
根据汇编指令和机器指令的翻译一一对应

链接

修正指令对符号地址的引用
主要包括:地址和空间分配, 符号决议, 重定位

目标文件分析

目标文件是指编译后未进行链接的中间文件, 格式和可执行文件几乎一样, 在Windows称为PE-COFF, linux下称为ELF

目标文件按照节(section)或者段(segment)形式存储, 目标文件的开头是一个文件头, 描述了整个文件的属性, 静态链接还是动态链接, 入口地址等, 还包含一个段表, 段表用来描述各个段的属性和地址偏移.
常见的段名有:

注意:
源代码编译后会把程序指令和程序数据分成两个段, 即.text和.data, 这样做的优点是:
1.数据和指令分别映射到两个虚拟内存区域, 读写权限就可以分开, 防止指令被意外改写
2.提高程序的局部性, 有利于CPU缓存命中率的提高
3.对于只读数据和只读指令, 可以进程共享资源, 节省空间

.bbs段记录了未初始化的变量, 只是预留了位置, 没有内容, 因此在文件中不占据空间, 但是在链接器装载后的虚拟地址中是要分配虚拟地址空间的. 其中区别在于, 未初始化的变量不会增加可执行文件的大小, 但是会增加程序运行时的空间.

符号

函数和变量称为符号, 函数名和变量名就是符号名.
链接器的过程就是把多个不同的目标文件拼装在一起, 如果目标文件之间有引用关系, 那么就需要解析外部符号, 链接器主要通过符号表和重定位表实现.

静态链接

多个源代码文件编译后生成多个.o目标文件, 静态链接就是将多个目标文件链接在一起最终形成一个可执行文件.

相似段合并

将所有输入的目标文件的相同段合并在一起, 比如多个.text段合并为一个大的.text段. 链接器会扫描所有的输入文件, 读取段首信息, 将目标文件中的所有符号的定义和引用收集到一个全局符号表里. 简单来说, 链接器合并了多个目标文件, 并建立了全局符号映射关系

符号解析和重定位

链接之前, 目标文件引用的外部符号都标记为undefined, 链接时, 通过全局符号表替换所有undefined符号. 除了符号的重定位, 还有虚拟地址VMA(Virtual Memory Address)的分配, 即链接后, 各个段分配了虚拟地址. 各个段内的符号根据偏移量计算符号地址

注意:
虚拟地址并不是从0地址开始分配, 不同操作系统有不同的分配规则, 如Linux下默认分配的起始地址是0x08048000

Common块

Common块是编译器用来处理弱符号的
强符号与弱符号
编译器默认函数和初始化了的全局变量是强符号, 未初始化的全局变量为弱符号, 也可以使用__ attribute__(weak)定义一个强符号为弱符号, 编译器对强弱符号有以下规则:

注意: 强符号和弱符号的并集并不是全部的符号, 很多符号既不是强符号, 也不是弱符号

直接导致需要Common机制的原因是编译器和链接器允许不同类型的弱符号存在, 但最本质的原因还是链接器不支持符号类型, 即链接器无法判断各个符号的类型是否一致

链接过程控制

链接过程可以使用ld链接脚本控制, 自定义链接过程

上一篇 下一篇

猜你喜欢

热点阅读