深入 C 语言和程序运行原理
于航
PayPal 技术专家
21121 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
深入 C 语言和程序运行原理
15
15
1.0x
00:00/00:00
登录|注册

27|编译器在链接程序时发生了什么?

可以在链接时一次性加入到程序中
静态库文件是一组打包在一起的目标文件
S(符号实际地址)+ A(Addend)- P(被修改的位置)
R_X86_64_64
R_X86_64_PLT32
R_X86_64_PC32
.rela.text
.rela.data
修正各个独立编译单元内对外部符号的引用地址
多个强符号同名则报错
同名弱符号任选其一
强符号优先于弱符号
未初始化的全局变量为弱符号
函数和已初始化的全局变量为强符号
对于基础且常见的公有代码库(如 libc、libm、libthread 等)采用动态链接
启动效率可能受影响
可移植性较差
可执行文件体积较小
依赖代码在程序执行时才加载到内存
对于应用程序独有的实现通常采用静态链接
执行效率不受影响
可移植性好
可执行文件体积较大
在用户准备执行程序前,所有依赖代码已经成为可执行文件的一部分
关系
计算方式
重定位类型(X86-64)
重定位表
目的
解析规则
强符号与弱符号
使用场景
特点
定义
使用场景
特点
定义
静态库文件 (.a) 与目标文件 (.o)
重定位
符号解析
动态链接
静态链接
思考题
静态链接的处理过程
静态链接与动态链接

该思维导图由 AI 生成,仅供参考

你好,我是于航。
我曾在 01 讲 的最后提到,C 代码的完整编译流程可以被分为四个阶段:代码预处理、编译优化、汇编,以及链接。在前三个阶段中,编译器会对输入的源代码文件依次进行分析、优化和转换,并生成可以在当前平台上使用的对象文件。紧接着,链接过程可以将程序依赖的所有对象文件进行整合,并生成最终的二进制可执行文件。
今天,我就来带你深入看看,这个“链接”的过程究竟是怎样执行的。按照发生时刻的不同,链接可以被分为编译时链接、加载时链接,以及运行时链接三种类型。其中,编译时链接又被称为“静态链接”,它是链接的一种最基本形态,今天我们便从它开始入手。
这一讲中,我会以 Linux 系统下的静态链接为例来进行介绍。虽然在其他操作系统中,这个过程的发生细节可能有所不同,但总的来看,静态链接在处理对象文件时采用的基本方法和目的都是一致的,你可以依此类推,举一反三。

静态链接 vs 动态链接

一个程序在被编译时,我们可以选择性地为其使用静态链接或动态链接。那么,二者的概念和使用场景有什么区别呢?这是经常被大家讨论的一个问题,接下来我们一起看看。
“静态链接”中的“静态”,实际上是指在用户准备执行这个程序前,正常运行所依赖的全部代码实现便已经“静静地躺在那里”,成为了整个可执行文件的一部分。相对地,使用动态链接编译的程序,编译器只会为这些依赖代码在可执行程序文件中留下用于临时占位的“槽”。而只有当用户开始调用程序时,相关代码才会被真正加载到内存。而这就是“动态”一词的重要体现之一。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了编译器在链接程序时的执行过程,主要围绕静态链接和动态链接的概念展开讨论。首先介绍了静态链接和动态链接的区别,以及它们对程序可执行文件的体积、执行效率和可移植性的影响。接着详细讲解了静态链接的处理过程,以 Linux 系统下的静态链接为例,从目标文件的基本结构到符号解析的具体流程进行了阐述。文章内容丰富,涵盖了编译器链接程序的技术细节,适合对编译器链接过程感兴趣的读者阅读。 在链接程序时,编译器首先进行符号解析,为每一个全局符号指定对应的“强弱”信息,并根据一定规则进行解析。接着进行重定位,将多个目标文件内相同类型的 Section 进行合并,并为这些 Section以及所有输出文件内使用到的符号指定运行时的 VAS 地址。链接器通过重定位表来修改对外部符号的引用地址,使得它们可以指向正确的运行时地址。最终,输出的可执行文件中,所有符号都有了正确的初始值和引用地址,程序可以被操作系统加载进内存,正常运行。 总的来看,静态链接被分为两个步骤:符号解析与重定位。符号解析是为应用程序使用的所有符号正确匹配对应符号定义的过程,而重定位过程中,链接器会将输入的多个目标文件的同类型 Section 进行合并,并为它们和所有程序使用到的符号分配运行时的 VAS 地址。紧接着,借助重定位表中的信息,链接器可以对上一步中得到的外部符号,进行地址及值上的修正。 本文内容涵盖了编译器链接程序的技术细节,对于对编译器链接过程感兴趣的读者具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入 C 语言和程序运行原理》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(9)

  • 最新
  • 精选
  • liu_liu
    老师有个疑问,S + A - P 中的 A 不是代表 Addend 吗?为什么是待修改位置上的值呢?

    作者回复: 观察的很细致!这里确实是我把 ELF32 与 ELF64 下的重定位方式搞混了,因为比如对于 R_386_32 和 R_X86_64_64 来说,两个的计算方式都是 S + A,但前者中的 A 实际指的是被修改内存所在位置上的值,本质上就是符号的 addend。而对于后者,也就是 ELF64 来说,addend 被单独拎出来与重定向条目放到一起了。这里我来纠正一下,稍后我也会修改下文章内容!感谢! 这里我把 ELF 标准中的相关段落也放出来,大家在后面的学习中也注意类似的问题。文档(http://www.skyfree.org/linux/references/ELF_Format.pdf)第 29 页:“The SYSTEM V architecture uses only Elf32_Rel relocation entries, the field to be relocated holds the addend.”

    2022-02-25
    5
  • 神佑小鹿
    链接器除了能将一组可重定位目标文件链接起来得到可执行目标文件以外,编译系统还提供一种机制,将所有相关的目标模块打包为一个单独文件,称为静态库(Static Library),可以作为链接器的输入。静态库是以存档(Achive)的文件格式存放在磁盘的,它是一组连接起来的可重定位目标文件的集合,有一个头部来描述每个成员目标文件的大小和位置,后缀为.a。使用静态库的优点有: 相关的函数可以被编译为独立的目标模块,然后封装成一个独立的静态库文件。 链接时,链接器只会复制静态库中被应用程序引用的目标模块,减少了可执行文件在磁盘和内存中的大小 应用程序员只需要包含较少的库文件名就能包含很多的目标模块,比如 ISO C99 中在 libc.a 静态库中包含了 atoi.o、scanf.o、strcpy.o 等可重定位目标模块,在 libm.a 静态库中包含了数学函数的目标模块。

    作者回复: 回答的不错!

    2022-05-11
    2
  • zxk
    在此基础之上,再向“右侧”移动 3 个字节,我们便可得到重定向的修改位置,即 0x400541。 老师想问下, 为什么需要向“右侧”移动 3 个字节?

    作者回复: 因为从左侧可以看到,这一行的起始字节位置是 “0x40053e”,而实际发生重定向的位置是在这里后面数三个字节的地方,所以需要再加 3,得到正确的地址 “0x400541”。

    2022-04-24
    2
    1
  • 神佑小鹿
    “可以看到,这个地址为十六进制值 0x601020。紧接着,A 表示符号 array 在重定位条目中的 Addend,即 -4。最后,P 表示当前重定向条目在输入文件中的修改位置。同样地,使用 objdump 命令,我们可以得到这个值。” 这个 P 是不是应该是符号占位符在可执行文件中的内存地址??

    作者回复: 这里 P 实际上就是符号重定位想要去修改的那个符号,在文件中的偏移位置。

    2022-05-11
  • LDxy
    一个.a静态库文件可以包含多个.o目标文件
    2022-02-25
    3
  • 连瑞龙
    原文中: gcc main.c sum.c -o main # 生成可执行文件 main; gcc -c main.c -o main.o # 生成目标文件 main.o; gcc -c sum.c -o sum.o # 生成目标文件 sum.o; 这部分内容应该调整下顺序,否则报错后初学者可能不知道原因: gcc main.c sum.c -o main # 生成可执行文件 main; gcc -c sum.c -o sum.o # 生成目标文件 sum.o; gcc -c main.c -o main.o # 生成目标文件 main.o;
    2024-01-05归属地:北京
  • Luke
    .o文件是编译器生成的文件,也是链接器的输入文件。 .a是linux下的静态库文件,gcc调用ar命令将多个.o文件合并,在GNU/Linux生态下,.a采用archive格式。 Windows平台上的.a和.lib都是采用COFF格式(Common Object File Format)。 记得以前用Borland平台的时候,还有tlb格式,Borland还提供了相关的工具(比如tlbimp)进行多种object格式之间的转换。
    2022-10-03归属地:江苏
  • Jason Yu 于航
    文中我们提到的重定向与重定位实际上都是指 Location,它们是同样的意思。这里我在写这篇文章时没注意到翻译的一致性,大家可以注意一下哈。
    2022-05-15
  • 神佑小鹿
    这个 “call” 指令是一条近址相对位移调用指令,它后面跟的是调用指令的下一条指令的偏移量
    2022-05-14
收起评论
显示
设置
留言
9
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部