静态链接和动态链接简述

本文详细介绍了静态链接和动态链接的区别。静态链接将所有依赖的库合并成一个可执行文件,而动态链接则在运行时加载共享库,节省磁盘和内存空间。静态链接涉及符号解析和重定位,动态链接利用GOT和PLT实现进程间的代码共享但数据不共享。动态链接的延迟绑定机制确保了效率和灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于对静态链接和动态链接的概念已经动作有所不了解,因此特意写了这篇文章进行初步的梳理。主要参考《深入理解计算机系统》这本书。

静态链接

静态链接概念

静态链接以一组可重定位的目标文件为输入,生成一个完全链接的可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节组成。
为了创建可执行文件,链接器必须完成两个主要任务:符号解析和重定位。

符号解析

链接器解析符号表.symtab,将每个符号引用和符号定义联系起来。

静态库

静态库是一系列可重定位目标文件的集合,在Linux系统下,通过ar工具将其打包为一个.a文件。
静态库出现的原因:使用静态库而不是直接使用可重定位的目标文件的原因在于,如果直接使用一个大的可重定位目标文件,那么链接器会将整个可重定位目标文件链接到可执行文件中,如果由多个可执行文件,就会导致每个可执行文件都有这样的拷贝,可执行文件的大小大大增加。而静态库打包了一组可重定位目标文件,在链接的时候,只会拷贝那些被程序引用的目标模块。
静态库解析顺序:在符号解析阶段,链接器从左到右来扫描可重定位目标文件和存档文件。

重定位

合并输入模块,为每个符号分配运行时地址。

重定位表目

汇编器遇到对最终位置未知的目标引用的时候,就会生成一个可重定位的表目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。.relo.text.relo.data这两个节存放了这些表目。

重定位流程

.o文件中,可以看出符号引用位置的值都是未知的,因此需要在重定位的时候,将其填充为正确的地址。如main函数的地址d处的4个全为0的字节,这部分需要在链接阶段通过链接器将array的地址填充进去。

   d:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi      # 14 <main+0x14>
test@iZuf6bogmr00a004vw0sbeZ:~/ctest/csapp_test$ objdump -S main.o

main.o:     文件格式 elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
 ************************************************************************/
int sum(int *a, int n);

int array[2] = {1, 2};

int main() {
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
    int val = sum(array, 2);
   8:   be 02 00 00 00          mov    $0x2,%esi
   d:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 14 <main+0x14>
  14:   e8 00 00 00 00          callq  19 <main+0x19>
  19:   89 45 fc                mov    %eax,-0x4(%rbp)
    return val;
  1c:   8b 45 fc                mov    -0x4(%rbp),%eax
}
  1f:   c9                      leaveq
  20:   c3                      retq

动态链接

一文看懂动态链接
动态链接和静态链接不同,动态链接在链接阶段并不会将共享库拷贝到可执行文件中,而是在程序加载或者运行时进行通过动态链接器进行加载。

大概思路是这样,程序a和b都依赖模块c,那么等程序a运行的时候,才链接c,c此时被加载进内存。程序b运行时,直接跟内存中的c进行链接,不再单独加载。 这样,无论是在磁盘还是内存中,都只有一份c模块,完美。

这里需要强调一下,这里所谓的共享,并不是共享整个Lib.c的内容,而是特指共享它的代码部分。 对于Lib.c中的数据部分,每个进程都需要一份自己的拷贝,因为它们可能需要独立地修改Lib.c中的数据。
在这里插入图片描述

动态链接的GOT和PLT表

动态库有两个特性,一是所有进程共享同一个动态库,即整个内存空间中只有一个动态库被加载;二是,动态库共享的是代码,而其数据区是不共享的,每个进程有自己单独的数据区。
GOT
因此动态库代码区如果引用到了数据区的数据,需要使用GOT来确定这个数据,保证每个进程读取到的是自己进程的数据。

PLT
可执行程序在调用到动态库的函数时,是不知道其地址的,在链接阶段也不知道。只有在程序加载进内存调用到动态链接器的时候,才知道函数的地址,但是此时我们是在加载阶段,并不能直接修改代码区的数据,因此可以定义一个标记指向数据区的地址,这个地址保存着被调函数的地址,可以在动态加载的时候被修改,这个区域就叫PLT。(其实上述PLT的功能和GOT的功能是一样的),编译器加入PLT的主要作用是实现了延迟绑定。
延迟绑定
在一开始的时候,GOT表中的函数地址并不是指向真的函数。可执行程序第一次执行到共享库的函数的时候,会先调用动态链接器,将GOT表中的函数地址修改为真正的函数地址,等到第二次执行到共享库函数的时候,会直接调用到GOT的地址,而不会再调用动态链接器了。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值