8.库制作与原理

发布于:2025-06-08 ⋅ 阅读:(16) ⋅ 点赞:(0)

1.什么是库

库是写好的现有的,成熟的,可以复⽤的代码。
本质上来说库是⼀种可执⾏代码的⼆进制形式,可以被操作系统载⼊内存执⾏。库有两种:
• 静态库 .a[Linux]、.lib[windows]
• 动态库 .so[Linux]、.dll[windows]

2.静态库

• 静态库(.a):程序在编译链接的时候把库的代码链接到可执⾏⽂件中,程序运⾏的时候将不再
需要静态库。

• ⼀个可执⾏程序可能⽤到许多的库,这些库运⾏有的是静态库,有的是动态库,⽽我们的编译默
认为动态链接库,只有在该库下找不到动态.so的时候才会采⽤同名静态库。我们也可以使⽤ gcc
的 -static 强转设置链接静态库。

2.1 静态库的生成 

静态库的本质是库源代码形成的obj进行打包!静态库合并自己的代码到可执行程序,一旦形成,就不再依赖静态库了,这时查看依赖库信息(ldd)查不到对静态库的依赖!

ar 是 gnu 归档⼯具, rc 表⽰ (replace and create)

$ ar -tv libmystdio.a
rw-rw-r-- 1000/1000 2848 Oct 29 14:35 2024 my_stdio.o
rw-rw-r-- 1000/1000 1272 Oct 29 14:35 2024 my_string.o

• t: 列出静态库中的⽂件
• v:verbose 详细信息

 2.2 静态库的使用

要使用一个第三方库,我们必须明确告诉编译器我们正在使用哪一个库 。

库 = 头文件(.h) + 库文件(给别人提供库) 。*库的使用需要搜索(利用L/l)所以库其实就是.o文件打了个包;如果把我们自己的库放到系统路径下,那么我们就不用再用L来指定路径了;

库安装:本质是把头文件(/usr/include)和 库文件(/lib64)拷贝到系统的指定的,默认的,编译器能找到的路径下!!

 

 这个写的好像不太对啊啊啊啊啊

3.动态库

• 动态库(.so):程序在运⾏的时候才去链接动态库的代码,多个程序共享使⽤库的代码。
• ⼀个与动态库链接的可执⾏⽂件仅仅包含它⽤到的函数⼊⼝地址的⼀个表,⽽不是外部函数所在⽬
标⽂件的整个机器码
• 在可执⾏⽂件开始运⾏以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,
这个过程称为动态链接(dynamic linking)
• 动态库可以在多个程序间共享,所以动态链接使得可执⾏⽂件更⼩,节省了磁盘空间。操作系统采
⽤虚拟内存机制允许物理内存中的⼀份动态库被要⽤到该库的所有进程共⽤,节省了内存和磁盘空
间。

3.1 配置动态库 

 静态库使用ar进行打包-o,动态库使用gcc/g++打包-o(因为动态库是主流)

 gcc -c -fPIC mystdio.c -o mystdio.o 。这样生成的.o文件就是与位置无关的。

 

 可恶的水印啊啊啊啊啊啊啊啊啊

所以我们安装开发环境,一方面安装写代码的东西vim...编译器....,另一方面还要把这门语言的头文件库文件安装下来!!!

3.2 查找动态库(将库与系统关联)

 2.内部建立软链接

ln:是创建链接的命令,ln命令有两种链接类型,硬链接和软链接(符号链接 )
-s:表示创建软链接(符号链接 )

 3.配置环境变量

 要想环境变量一直有效,需要更改配置文件。

4.更改系统配置文件

 在该目录下,sudo创建一个文件,vim文件直接将动态库的绝对路径粘贴到该文件中即可,还需要ldconfig更新

4.使用外部库

就是配置头文件和库文件!!!然后再把对应的代码粘贴过来

5.目标文件

 编译和链接这两个步骤,在Windows下被我们的IDE封装的很完美,我们⼀般都是⼀键构建⾮常⽅便。编译的过程其实就是将我们程序的源代码翻译成CPU能够直接运⾏的机器代码。

⽐如:在⼀个源⽂件 hello.c ⾥便简单输出"hello world!",并且调⽤⼀个run函数,⽽这个函数被
定义在另⼀个原⽂件 code.c 中。这⾥我们就可以调⽤ gcc -c 来分别编译这两个原⽂件。
// hello.c
#include<stdio.h>
void run();
int main() {
printf("hello world!\n");
run();
return 0;
}
// code.c
#include<stdio.h>
void run() {
printf("running...\n");
}
// 编译两个源⽂件
$ gcc -c hello.c
$ gcc -c code.c
$ ls
code.c code.o hello.c hello.o
1
可以看到,在编译之后会⽣成两个扩展名为 .o 的⽂件,它们被称作⽬标⽂件。要注意的是如果我们修改了⼀个原⽂件,那么只需要单独编译它这⼀个,⽽不需要浪费时间重新编译整个⼯程。⽬标⽂件是⼀个⼆进制的⽂件,⽂件的格式是 ELF ,是对⼆进制代码的⼀种封装。
$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
## file命令⽤于辨识⽂件类型。

6.ELF文件

有以下四种⽂件其实都是ELF⽂件:
• 可重定位⽂件(Relocatable File) :即 xxx.o ⽂件。包含适合于与其他⽬标⽂件链接来创
建可执⾏⽂件或者共享⽬标⽂件的代码和数据。
• 可执⾏⽂件(Executable File) :即可执⾏程序。
• 共享⽬标⽂件(Shared Object File) :即 xxx.so⽂件。
• 内核转储(core dumps) ,存放当前进程的执⾏上下⽂,⽤于dump信号触发。


⼀个ELF⽂件由以下四部分组成:
• ELF头(ELF header) :描述⽂件的主要特性。其位于⽂件的开始位置,它的主要⽬的是定位⽂
件的其他部分。
• 程序头表(Program header table) :列举了所有有效的段(segments)和他们的属性。表⾥
记着每个段的开始的位置和位移(offset)、⻓度,毕竟这些段,都是紧密的放在⼆进制⽂件中,
需要段表的描述信息,才能把他们每个段分割开。
• 节头表(Section header table) :包含对节(sections)的描述。
• 节(Section ):ELF⽂件中的基本组成单位,包含了特定类型的数据。ELF⽂件的各种信息和
数据都存储在不同的节中,如代码节存储了可执⾏代码,数据节存储了全局变量和静态数据等。

##最常⻅的节:
• 代码节(.text):⽤于保存机器指令,是程序的主要执⾏部分。
• 数据节(.data):保存已初始化的全局变量和局部静态变量。

7.ELF从形成到加载轮廓

7.1 ELF形成可执行

• step-1:将多份 C/C++ 源代码,翻译成为⽬标 .o ⽂件
• step-2:将多份 .o ⽂件section进⾏合并

注意:
• 实际合并是在链接时进⾏的,但是并不是这么简单的合并,也会涉及对库合并,此处不做
过多追究

7.2 ELF可执行文件加载

• ⼀个ELF会有多种不同的Section,在加载到内存的时候,也会进⾏Section合并,形成segment
• 合并原则:相同属性,⽐如:可读,可写,可执⾏,需要加载时申请空间等.
• 这样,即便是不同的Section,在加载到内存中,可能会以segment的形式,加载到⼀起
• 很显然,这个合并⼯作也已经在形成ELF的时候,合并⽅式已经确定了,具体合并原则被记录在了
ELF的 程序头表(Program header table) 中

 页表将虚拟转化为物理内存时怎么知道文件的权限呢?其实就是根据ELF文件;ELF文件的这些ELF Header等也都用结构体打包。

# 查看可执⾏程序的section
$ readelf -S a.out

# 查看section合并的segment
$ readelf -l a.out

为什么要将section合并成为segment

• Section合并的主要原因是为了减少⻚⾯碎⽚,提⾼内存使⽤效率。如果不进⾏合并,
假设⻚⾯⼤⼩为4096字节(内存块基本⼤⼩,加载,管理的基本单位),如果.text部分
为4097字节,.init部分为512字节,那么它们将占⽤3个⻚⾯,⽽合并后,它们只需2个
⻚⾯。
• 此外,操作系统在加载程序时,会将具有相同属性的section合并成⼀个⼤的
segment,这样就可以实现不同的访问权限,从⽽优化内存管理和权限访问控制。
对于 程序头表 和 节头表 ⼜有什么⽤呢,其实 ELF ⽂件提供 2 个不同的视图/视⻆来让我们理解这
两个部分:
• 链接视图(Linking view) - 对应节头表 Section header table
◦ ⽂件结构的粒度更细,将⽂件按功能模块的差异进⾏划分,静态链接分析的时候⼀般关注的
是链接视图,能够理解 ELF ⽂件中包含的各个部分的信息。
◦ 为了空间布局上的效率,将来在链接⽬标⽂件时,链接器会把很多节(section)合并,规整
成可执⾏的段(segment)、可读写的段、只读段等。合并了后,空间利⽤率就⾼了,否
则,很⼩的很⼩的⼀段,未来物理内存⻚浪费太⼤(物理内存⻚分配⼀般都是整数倍⼀块给
你,⽐如4k),所以,链接器趁着链接就把⼩块们都合并了。
• 执⾏视图(execution view) - 对应程序头表 Program header table
◦ 告诉操作系统,如何加载可执⾏⽂件,完成进程内存的初始化。⼀个可执⾏程序的格式中,
⼀定有 program header table 。
• 说⽩了就是:⼀个在链接时作⽤,⼀个在运⾏加载时作⽤。

从 链接视图 来看:
• 命令 readelf -S hello.o 可以帮助查看ELF⽂件的 节头表。
• .text节 :是保存了程序代码指令的代码节。
• .data节 :保存了初始化的全局变量和局部静态变量等数据。
• .rodata节 :保存了只读的数据,如⼀⾏C语⾔代码中的字符串。由于.rodata节是只读的,所
以只能存在于⼀个可执⾏⽂件的只读段中。因此,只能是在text段(不是data段)中找到.rodata
节。
• .BSS节 :为未初始化的全局变量和局部静态变量预留位置
• .symtab节 : Symbol Table 符号表,就是源码⾥⾯那些函数名、变量名和代码的对应关系。
• .got.plt节 (全局偏移表-过程链接表):.got节保存了全局偏移表。.got节和.plt节⼀起提供
了对导⼊的共享库函数的访问⼊⼝,由动态链接器在运⾏时进⾏修改。对于GOT的理解,我们后
⾯会说。
◦ 使⽤ readelf 命令查看 .so ⽂件可以看到该节。

从 执⾏视图 来看:
• 告诉操作系统哪些模块可以被加载进内存。
• 加载进内存之后哪些分段是可读可写,哪些分段是只读,哪些分段是可执⾏的。

8.理解连接与加载

8.1 静态链接

• ⽆论是⾃⼰的.o, 还是静态库中的.o,本质都是把.o⽂件进⾏连接的过程
• 所以:研究静态链接,本质就是研究.o是如何链接的
• objdump -d 命令:将代码段(.text)进⾏反汇编查看
• hello.o 中的 main 函数不认识 printf和run 函数
• code.o 不认识 printf 函数

我们可以看到这⾥的call指令,它们分别对应之前调⽤的printf和run函数,但是你会发现他们的跳转地
址都被设成了0。那这是为什么呢?
其实就是在编译 hello.c 的时候,编译器是完全不知道 printf 和 run 函数的存在的,⽐如他们
位于内存的哪个区块,代码⻓什么样都是不知道的。因此,编辑器只能将这两个函数的跳转地址先暂
时设为0。
这个地址会在哪个时候被修正?链接的时候!为了让链接器将来在链接时能够正确定位到这些被修正
的地址,在代码块(.data)中还存在⼀个重定位表,这张表将来在链接的时候,就会根据表⾥记录的
地址将其修正。

--用readef -s main.exe ..... 来查看

最终:

1.两个.o的代码段合并到了一起,并进行了统一的编址

2.链接的时候,会修改.o中没有确定的函数地址,在合并完成之后,进行相关call地址,完成代码调用(链接时地址重定位!!)

静态链接就是把库中的.o进⾏合并,和上述过程⼀样
所以链接其实就是将编译之后的所有⽬标⽂件连同⽤到的⼀些静态库运⾏时库组合,拼装成⼀个独⽴
的可执⾏⽂件。其中就包括我们之前提到的地址修正,当所有模块组合在⼀起之后,链接器会根据我
们的.o⽂件或者静态库中的重定位表找到那些需要被重定位的函数全局变量,从⽽修正它们的地址。这
其实就是静态链接的过程。

 所以,链接过程中会涉及到对.o中外部符号进⾏地址重定位

8.2 ELF加载与进程地址空间

8.2.1 虚拟地址/逻辑地址

问题:
• ⼀个ELF程序,在没有被加载到内存的时候,有没有地址呢?
• 进程mm_struct、vm_area_struct在进程刚刚创建的时候,初始化数据从哪⾥来的?

答案:
• ⼀个ELF程序,在没有被加载到内存的时候,本来就有地址,当代计算机⼯作的时候,都采⽤"平坦
模式"进⾏⼯作。所以也要求ELF对⾃⼰的代码和数据进⾏统⼀编址。下⾯是 objdump -S 反汇编
之后的代码

最左侧的就是ELF的虚拟地址,其实,严格意义上应该叫做逻辑地址(起始地址+偏移量),
但是我们认为起始地址是0.也就是说,其实虚拟地址在我们的程序还没有加载到内存的时候,
就已经把可执 ⾏程序进⾏统⼀编址了.

• 进程mm_struct、vm_area_struct在进程刚刚创建的时候,初始化数据从哪⾥来的?从ELF各个
segment来,每个segment有⾃⼰的起始地址和⾃⼰的⻓度,⽤来初始化内核结构中的[start, end]
等范围数据,另外在⽤详细地址,填充⻚表.
所以:虚拟地址机制,不光光OS要⽀持,编译器也要⽀持

8.2.2 重新理解进程虚拟地址空间

ELF 在被编译好之后,会把⾃⼰未来程序的⼊⼝地址记录在ELF header的Entry字段中:

 解释一下大体流程:一开始一个ELF可执行程序在磁盘中时(还未加载到内存中时)就已经有虚拟地址了;然后程序要加载到内存中,于CPU中的EIP(程序计数器:当前正在执行的指令的下一条指令的地址)读取程序入口地址,先根据 CR3(控制寄存器,主要用于内存管理,特别是与分页机制)寄存器中保存的当前进程的页目录表地址,找到页目录表,然后通过页目录表和页表来建立虚拟地址与物理地址的映射关系,这里是用MMU(用于管理虚拟存储器和物理存储器的控制部件)将虚拟地址转换为物理地址。同时MMU将物理地址传给物理内存;当CPU 根据程序执行的需要向物理内存请求数据,物理内存响应请求将数据传给 CPU,这样就形成了闭环!!!

 所以,虚拟地址空间技术,需要:OS,编译器,CPU硬件三者共同支持!!!

附:

 

gcc将当前目录下所有的.o目标文件进行链接,生成一个名为myexe的可执行文件 。 不过如之前遇到的问题,若目标文件中缺少main函数定义,链接时就会报错。

8.3 动态链接与动态加载 

8.3.1 进程间如何共享库

8.3.2 动态链接

$ ldd hello
linux-vdso.so.1 => (0x00007fffeb1ab000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)
# ldd命令⽤于打印程序或者库⽂件所依赖的共享库列表。
这⾥的 libc.so是C语⾔的运⾏时库,⾥⾯提供了常⽤的标准输⼊输出⽂件字符串处理等等这些功能。那为什么编译器默认不使⽤静态链接呢?静态链接会将编译产⽣的所有⽬标⽂件,连同⽤到的各种库,合并形成⼀个独⽴的可执⾏⽂件,它不需要额外的依赖就可以运⾏。照理来说应该更加⽅便才对是吧?
静态链接最⼤的问题在于⽣成的⽂件体积⼤,并且相当耗费内存资源。随着软件复杂度的提升,我们的操作系统也越来越臃肿,不同的软件就有可能都包含了相同的功能和代码,显然会浪费⼤量的硬盘空间。
这个时候,动态链接的优势就体现出来了,我们可以将需要共享的代码单独提取出来,保存成⼀个独⽴的动态链接库,等到程序运⾏的时候再将它们加载到内存,这样不但可以节省空间,因为同⼀个模块在内存中只需要保留⼀份副本,可以被不同的进程所共享。
动态链接实际上将链接的整个过程推迟到了程序加载的时候。
⽐如我们去运⾏⼀个程序,操作系统会⾸先将程序的数据代码连同它⽤到的⼀系列动态库先加载到内存,其中每个动态库的加载地址都是不固定的,操作系统会根据当前地址空间的使⽤情况为它们动态分配⼀段内存。当动态库被加载到内存以后,⼀旦它的内存地址被确定,我们就可以去修正动态库中的那些函数跳转地址了。
8.3.2.1我们的可执行程序被编译器动了手脚
$ ldd /usr/bin/ls
    linux-vdso.so.1 (0x00007fffdd85f000)
    libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1
(0x00007f42c025a000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f42c0068000)
    libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0
(0x00007f42bffd7000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f42bffd1000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f42c02b6000)//看这个
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0
(0x00007f42bffae000)
$ ldd main.exe
    linux-vdso.so.1 (0x00007fff231d6000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f197ec3b000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f197ee3e000)//这个
在C/C++程序中,当程序开始执⾏时,它⾸先并不会直接跳转到 main 函数。实际上,程序的⼊⼝点是 _start ,这是⼀个由C运⾏时库(通常是glibc)或链接器(如ld)提供的特殊函数。
_start 函数中,会执⾏⼀系列初始化操作,这些操作包括:
1. 设置堆栈:为程序创建⼀个初始的堆栈环境。
2. 初始化数据段:将程序的数据段(如全局变量和静态变量)从初始化数据段复制到相应的内存位
置,并清零未初始化的数据段。
3. 动态链接:这是关键的⼀步, _start 函数会调⽤动态链接器的代码来解析和加载程序所依赖的
动态库(shared libraries)。动态链接器会处理所有的符号解析和重定位,确保程序中的函数调
⽤和变量访问能够正确地映射到动态库中的实际地址。
动态链接器:
◦ 动态链接器(如ld-linux.so)负责在程序运⾏时加载动态库。
比特就业课
◦ 当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。
环境变量和配置⽂件:
◦ Linux系统通过环境变量(如LD_LIBRARY_PATH)和配置⽂件(如/etc/ld.so.conf及其⼦配置
⽂件)来指定动态库的搜索路径。
◦ 这些路径会被动态链接器在加载动态库时搜索。
缓存⽂件:
◦ 为了提⾼动态库的加载效率,Linux系统会维护⼀个名为/etc/ld.so.cache的缓存⽂件。
◦ 该⽂件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会⾸先
搜索这个缓存⽂件。
4. 调⽤ __libc_start_main :⼀旦动态链接完成, _start 函数会调⽤
__libc_start_main (这是glibc提供的⼀个函数)。 __libc_start_main 函数负责执⾏
⼀些额外的初始化⼯作,⽐如设置信号处理函数、初始化线程库(如果使⽤了线程)等。
5. 调⽤ main 函数:最后, __libc_start_main 函数会调⽤程序的 main 函数,此时程序的执
⾏控制权才正式交给⽤⼾编写的代码。
6. 处理 main 函数的返回值:当 main 函数返回时, __libc_start_main 会负责处理这个返回
值,并最终调⽤ _exit 函数来终⽌程序。
8.3.2.2 动态库中的相对地址
动态库为了随时进⾏加载,为了⽀持并映射到任意进程的任意位置,对动态库中的⽅法,统⼀编址,采⽤相对编址的⽅案进⾏编制的(其实可执⾏程序也⼀样,都要遵守平坦模式,只不过exe是直接加载的)。
# ubuntu下查看任意⼀个库的反汇编
objdump -S /lib/x86_64-linux-gnu/libc-2.31.so | less

# Cetnos下查看任意⼀个库的反汇编
$ objdump -S /lib64/libc-2.17.so | less
8.3.2.3 我们的程序,怎么和库具体映射起来的;怎么进行库函数的调用
注意:
• 动态库也是⼀个⽂件,要访问也是要被先加载,要加载也是要被打开的

• 让我们的进程找到动态库的本质:也是⽂件操作,不过我们访问库函数,通过虚拟地址进
⾏跳转访问的,所以需要把动态库映射到进程的地址空间中

结论1:库函数的调用,也是在进程的虚拟地址空间范围内调用

结论2:动态库被映射到进程的任意位置(一般是共享区),我们的进程都能调用

结论3:多进程映射的时候,每个进程都会把动态库映射到自己的地址空间,但是起始空间可能不同,但不妨碍,任意进程访问库函数!

注意:
• 库已经被我们映射到了当前进程的地址空间中
• 库的虚拟起始地址我们也已经知道了
• 库中每⼀个⽅法的偏移量地址我们也知道
• 所有:访问库中任意⽅法,只需要知道库的起始虚拟地址+⽅法偏移量即可定位库中的⽅
法
• ⽽且:整个调⽤过程,是从代码区跳转到共享区,调⽤完毕在返回到代码区,整个过程完
全在进程地址空间中进⾏的.
8.3.2.4 全局偏移量表GOT(global offset table)
注意:
• 也就是说,我们的程序运⾏之前,先把所有库加载并映射,所有库的起始虚拟地址都应该
提前知道
• 然后对我们加载到内存中的程序的库函数调⽤进⾏地址修改,在内存中⼆次完成地址设置
(这个叫做加载地址重定位)

• 但是,修改的是代码区?不是说代码区在进程中是只读的吗?怎么修改?能修改吗?

所以:动态链接采⽤的做法是在 .data (可执⾏程序或者库⾃⼰)中专⻔预留⼀⽚区域⽤来存放函数的跳转地址,它也被叫做全局偏移表GOT,表中每⼀项都是本运⾏模块要引⽤的⼀个全局变量或函数的地址。

因为.data区域是可读写的,所以可以⽀持动态进⾏修改

1. 由于代码段只读,我们不能直接修改代码段。但有了GOT表,代码便可以被所有进程共享。但在不同进程的地址空间中,各动态库的绝对地址、相对位置都不同。反映到GOT表上,就是每个进程的每个动态库都有独⽴的GOT表,所以进程间不能共享GOT表。
2.  在单个.so下,由于GOT表与 .text 的相对位置是固定的,我们完全可以利⽤CPU的相对寻址来找 到GOT表。
3. 在调⽤函数的时候会⾸先查表,然后根据表中的地址来进⾏跳转,这些地址在动态库加载的时候会 被修改为真正的地址。
4. 这种⽅式实现的动态链接就被叫做 PIC 地址⽆关代码 。换句话说,我们的动态库不需要做任何修 改,被加载到任意内存地址都能够正常运⾏,并且能够被所有进程共享,这也是为什么之前我们给 编译器指定-fPIC参数的原因,PIC=相对编址+GOT。
8.3.2.5 库间依赖(了解)
• 不仅仅有可执⾏程序调⽤库
• 库也会调⽤其他库!!库之间是有依赖的,如何做到库和库之间互相调⽤也是与地址⽆关的呢??
• 库中也有.GOT,和可执⾏⼀样!这也就是为什么⼤家为什么都是ELF的格式!

由于动态链接在程序加载的时候需要对⼤量函数进⾏重定位,这⼀步显然是⾮常耗时的。为了进⼀
步降低开销,我们的操作系统还做了⼀些其他的优化,⽐如延迟绑定,或者也叫PLT(过程连接表
(Procedure Linkage Table))。与其在程序⼀开始就对所有函数进⾏重定位,不如将这个过程
推迟到函数第⼀次被调⽤的时候,因为绝⼤多数动态库中的函数可能在程序运⾏期间⼀次都不会被
使⽤到。

总⽽⾔之,动态链接实际上将链接的整个过程,⽐如符号查询、地址的重定位从编译时推迟到了程序的运⾏时,它虽然牺牲了⼀定的性能和程序加载时间,但绝对是物有所值的。因为动态链接能够更有效的利⽤磁盘空间和内存资源,以极⼤⽅便了代码的更新和维护,更关键的是,它实现了⼆进制级别的代码复⽤。
解析依赖关系的时候,就是加载并完善互相之间的GOT表的过程.

总结:

• 静态链接的出现,提⾼了程序的模块化⽔平。对于⼀个⼤的项⽬,不同的⼈可以独⽴地测试和开发
⾃⼰的模块。通过静态链接,⽣成最终的可执⾏⽂件。

• 我们知道静态链接会将编译产⽣的所有⽬标⽂件,和⽤到的各种库合并成⼀个独⽴的可执⾏⽂件,
其中我们会去修正模块间函数的跳转地址,也被叫做编译重定位(也叫做静态重定位)。

• ⽽动态链接实际上将链接的整个过程推迟到了程序加载的时候。⽐如我们去运⾏⼀个程序,操作系
统会⾸先将程序的数据代码连同它⽤到的⼀系列动态库先加载到内存,其中每个动态库的加载地址
都是不固定的,但是⽆论加载到什么地⽅,都要映射到进程对应的地址空间,然后通过.GOT⽅式进
⾏调⽤(运⾏重定位,也叫做动态地址重定位)。


网站公告

今日签到

点亮在社区的每一天
去签到