第7章 链接

 链接将各种代码和数据片段收集并组合成一个单一文件,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时(源代码到机器代码),也可以执行于加载时(程序被加载器加载到内存并执行时),甚至可以执行于运行时,也就是由应用程序来执行。链接由链接器自动执行。
链接的优点
 链接器使得分离编译成为可能,不用将大型的应用程序组织为一个巨大的源文件。而是可以把它分解成小的模块,可以独立修改和编译这些模块。

7.1编译器驱动程序

linux> gcc -Og -o prog main.c sum.c
在这里插入图片描述
 GCC将源代码转化成可执行代码的流程
(1)C预处理器cpp扩展源代码,插入所有用#include命令指定的文件,并扩展声明的宏,并翻译成一个ASCII码的中间文件:.i;
(2)编译器ccl产生两个源代码的ASCII汇编代码:.s;
(3)汇编器as将汇编代码转化为二进制目标代码(可重定位目标文件) :.o ;(目标代码是机器代码的一种形式,它包含所有指令的二进制表示,但没有填入地址的全局值)
(4)链接器ld将目标代码与实现库函数的代码合并,最终产生可执行代码文件;
(5)shell调用加载器将可执行文件中的代码和数据复制到内存,然后将控制转移到这个程序的开头。

7.2静态链接

 像Linux LD这样的静态链接器以可重定位目标文件为输入,可执行目标文件为输出。链接器完成量主要任务:
(1)符号解析:将每个符号(对应与一个函数、一个全局变量或一个静态变量)引用与符号定义相关联(引用,定义相关联)
(2)重定位:将地址转变为运行时真实的地址,编译器和汇编器生成的是从地址0开始的代码和数据节。

7.3目标文件

 目标文件纯粹是字节块的集合,区分于源文件
 目标文件分为三种形式:
(1)可重定位目标文件:包含二进制代码和数据,可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件
(2)可执行目标文件:包含二进制代码和数据,可以被直接复制到内存并执行
(3)共享目标文件:特殊的可重定位目标文件,可以在加载或运行时动态链接

7.4可重定位目标文件(ELF)

 
 局部变量运行时保存在栈中,既不出现在.data中,也不出现在.bss中
 .bss可以看作是better save space,因为.bss不占实际空间,仅仅是占位符,未初始化变量不需要占据任何实际的磁盘空间,运行时在内存中分配为0.(区分.data和.bss
在这里插入图片描述

7.5符号和符号表

 静态变量:带有static声明的全局变量或者函数都是模块私有的,外部访问不了,文件内部都可以访问
 符号表(.symtab)有三种不同的符号:(对应本地非静态程序变量的符号在运行时由栈管理,链接器对此类符号不感兴趣)
(1)由模块m定义并能被其他模块引用的全局符号,如非静态的C函数和全局变量
(2)由其他模块定义并被模块m引用的全局符号,称为外部符号,对应外部定义的非静态的C函数和全局变量
(3)只被模块m定义和引用的局部符号,对应于带static属性的C函数和全局变量,这些符号在m中可见,不能被外部引用
在这里插入图片描述

 编译器可以预测时,在.data或.bss中为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号。
 编译器无法预测同名的全局符号时,也可以分配在COMMON中,将决定权留给链接器。
在这里插入图片描述

7.6符号解析

 作用:将每个引用与符号表中一个确定的符号定义关联起来。
 对局部变量的引用解析很简单,编译器确保他们有唯一的名字,静态局部变量也会有本地链接器符号(在符号表中)。
 对全局符号的引用解析更复杂,编译器遇到一个不是在当前模块定义的符号时,会假设该符号时再其他某个模块中定义的,生成一个链接器符号表条目,交给链接器处理,如果链接器在任何输入中都找不到,输出错误。全局变量名字相同会报错或选其中一个。
 如何实现重载:源代码中名字相同,编译器将其编码成不同的名字给链接器。
7.6.1链接器如何解析多重定义的全局符号
●强符号:函数和已初始化的全局变量
●弱符号:未初始化的全局变量

 规则:
(1)强符号不能同名
(2)强弱同名,选强
(3)弱符号同名,任选一个
7.6.2与静态库链接
 静态库:将所有相关的目标模块打包成一个单独的文件称为静态库,可以用做链接器的输入

 静态库的格式:在Linux以存档的特殊文件格式存放(.a)

 使用静态库的原因:例子见书7.6.2
(1)直接辨认:为了提供标准函数,但是C中标准函数太多,不能让编译器直接辨认并生成相应的代码
(2)统一打包所有库函数:如gcc main.c /usr/lib/libc.o
将所有的C函数放在一个可重定位目标模块中用于链接这个方法将编译器的实现与标准函数分开,但会造成
1.每个可执行文件都包含一份标准函数的完全副本
2.正在运行的程序内存中有这些副本
3.无论多小的改变都要重新编译整个源文件,非常麻烦
(3)每个模块单独创建:gcc main.c /usr/lib/printf.o /usr/lib/scanf.o将每个标准函数创建一个独立的可重定位文件,放在大家都知道的目录解决方法1的问题,但程序员链接时容易出错(要链接的文件太多)
 静态库(采用方法):所以将相关的函数编译为独立的目标模块,然后封装成一个单独的静态库文件。这样在链接时,链接器只复制被程序引用的目标模块。

 静态库的创建和使用
●创建:使用AR工具

gcc -c a.c b.c
ar rcs libv.a a.o b.o

●使用:libc.a默认传给链接器
在这里插入图片描述
在这里插入图片描述
7.6.3链接器如何使用静态库来解析引用
 链接器从左到右扫描文件,扫描中链接器维护一个可重定位目标文件的集合E(这个集合的文件合并起来形成可执行文件),一个未解析的符号(即引用了但未定义的符号)集合U,一个在前面输入文件中已定义的符号集合D
●对于每个输入文件f,链接器会判断f是目标文件还是存档文件(静态库)。如果f是目标文件,那么把f添加到E,修改U和D反映f中的符号定义和引用,并继续扫描。
●如果f是存档文件,链接器就尝试将U中的符号与存档匹配,如果存档m定义了符号来解析U中的引用,那么将m加到E中,并且修改U和D。对存档文件中所有的成员都进行这个过程直到U和D不再变化。不包含在E中的目标文件简单地丢弃。(解析了U中需要解析的符号的留下,其余的丢弃
●如果链接器完成扫描后U非空,那么就报错。U为空就合并E中文件输出可执行文件。
 库要放在命令行的结尾,如果放在开头,U中还没有未解析符号时库肯定会被丢弃,最后U肯定非空,报错。

7.7重定位

 完成符号解析之后,链接器知道输入中代码和数据的确切大小,可以开始重定位,分为两步
(1)重定位节和符号定义:将所有相同类型的节合并为同一类型的新的聚合节。如将所有输入的.data节合并,成为可执行文件的.data节。然后链接器将运行时内存地址赋给新的聚合节,赋给每个节与符号。这一步完成时,程序中每条指令和全局变量都有唯一运行时内存地址了。(合并、赋地址
(2)重定位节中的符号引用:修改代码和数据中对每个符号的引用,使得它们指向正确的运行时地址,这一步依赖于重定位条目。(修改引用指向正确运行地址
7.7.1重定位条目
 作用:让位置未知的引用指向正确运行地址
 格式:
在这里插入图片描述
 类型:
在这里插入图片描述
7.7.2重定位符号引用具体过程见书
 过程:
在这里插入图片描述

7.8可执行目标文件

 结构:程序用.init初始化代码。因为文件已完全链接,所以不再需要.rel在这里插入图片描述

7.9加载可执行目标文件

 加载:通过调用加载器将可执行目标文件的代码和数据从磁盘复制到内存,然后跳转到程序的入口来运行该程序。(如linux调用execve)
 运行时内存映像:
在这里插入图片描述
在这里插入图片描述

7.10动态链接共享库

 静态库的缺点:
(1)需要定期维护和更新
(2)几乎每个C程序都使用如printf这样的标准I/O函数,运行时,这些代码会被复制到每个运行进程的文本段中。在一个运行上百个进程的典型系统上,很浪费内存。
 共享库的作用:解决静态库缺点
 共享库的原理:可以在运行或加载时放到任意的内存位置,并和内存中的程序连接起来(复制进内存由进程共享,不复制进每个可执行文件,只需一个副本)。整个过程称为动态链接,由动态链接器完成。
 使用:
在这里插入图片描述
 过程:
在这里插入图片描述
在这里插入图片描述

7.11从应用程序加载和链接共享库

 共享库的应用:
●分发软件:微软应用开发者常利用共享库分发软件更新
●构建高性能web服务器:生成动态内容

 Linux系统使用dlopen函数加载和链接共享库filename
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值