%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
- [[#|❓静态库与动态库有何区别]]
- [[#使用示例|❓如何创建、使用动/静态库?]]
- [[#链接时的静态库声明顺序 🚨|❓链接时命令行中对静态库的声明顺序有何要求?]]
- [[#共享库的 "位置无关特性" 与 "延迟绑定"|❓共享库的 "位置无关特性" 是指什么?如何实现的?]]
- [[#共享库中函数的 "延迟绑定"|❓共享库函数地址的 "延迟绑定" 是指什么?如何实现的?]]
- [[#全局偏移量表 GOT]]
- [[#过程链接表 PLT]]
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
- 当存在多个进程链接到同一个共享库时,共享库在每个进程中的内存地址是否都相同?
- ❓ 如何查看 & 设置动态库的 "运行时搜索路径" ?
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 静态库与动态库总结
"库" 是一组**预编译的代码和数据集合**,封装成了**库文件**,**可供其他程序使用**(库自身无法独自运行)。
库文件根据**链接方式和用法**分为两种:「**==静态库==**」和 「**==动态库==**」
| | Unix-like 下后缀 | Windows 下后缀 | 链接时代码嵌入 | 程序运行时依赖库文件 | 代码共享 |
| ---------------- | ------------- | ----------- | ------- | ---------- | ---- |
| **静态库** | `.a` | `.lib` | ✔️ | ❌ | ❌ |
| **动态库**(**共享库**) | `.so` | `.dll` | ❌ | ✔️ | ✔ |
![[02-开发笔记/03-计算机基础/编译与链接/静态库与动态库#^squbw5]]
### 使用静态库、动态库的优缺点
也即 "静态链接"、 "动态链接" 的优缺点,参见 [[02-开发笔记/03-计算机基础/编译与链接/链接#「静态链接」与「动态链接」|链接-「静态链接」与「动态链接」]]
![[02-开发笔记/03-计算机基础/编译与链接/链接#^w3s7wr]]
### 使用示例
##### 静态库
```shell
# 创建静态库
g++ -c addvec.cpp mutvec.cpp
ar rcs -o libvector.a addvec.o mutvec.o
# 使用静态库
g++ -o prog main.cpp -L. -lvector # 方式一
g++ -o prog main.cpp ./libvector.a # 方式二
./prog # 运行程序
```
##### 动态库
```shell
# 创建共享库(动态链接库)
g++ -shared -fpic -o libvector.so addvec.cpp mutvec.cpp
# 使用共享库
g++ -o prog main.cpp -L. -lvector # 链接阶段引入动态库的重定位信息及符号信息.
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH # 设置动态链接器在运行时搜索动态库的路径
./prog # 运行程序, 将在运行时动态链接到共享库
```
<br><br><br>
# 静态库
> 静态库被设计于应用在 "**静态链接**" 的场景。
在 Linux 下,**静态库**以称为 "**==存档==" (archive)** 的文件格式保存(后缀`.a`),本质上即是**将一组==可重定位目标文件==进行打包**,其中具有一个特殊头部描述各个成员目标文件的大小和位置。
## 创建静态库
使用 `ar rcs <libname>` 命令可将一组 "**可重定位目标文件**" 打包生成**静态库**。

> [!example] 创建静态库示例
>
> - `gcc -c foo.c bar.c` :编译生成 "**可重定位目标文件**"
> - `ar rcs libmylib.a foo.o bar.o` :将**多个可重定位目标文件**打包成 "**静态库**" 文件 `libmylib.a`
> [!info] Linux 库的命名规范
>
> Linux 下要求**动/静态库的命名格式**必须为:**`lib[your_library_name][扩展名]`**,
> 其中 **lib 为前缀**,中间是**库名**,**扩展名为`.a`或 `.so`**。
>
> > [!NOTE] 链接器通过 `-l` 指定库名时,需省略 `lib` 前缀和`.a` 或 `.so` 后缀,链接器会自动补上。
> ^4pmzpt
<br>
## 使用静态库

程序开发时,通常使用一个静态库需要有:
1. **静态库对应的`.h` ==头文件==** (由库提供者提供,头文件中包含静态库中的**函数&变量等声明**)
2. **静态库文件本身(`.a`/`.lib` 文件)**
开发者通过**引入静态库对应的头文件**来**使用静态库中定义的符号**,然后再在链接时**由链接器完成对静态库文件的链接**。
> [!example] Linux gcc/g++ 编译时使用静态库:
>
> - **方式一**(**直接指定静态库文件**):`gcc -o myprogram main.c ./libmylib.a`
>
> ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-8EC2B0934CB2D20CB6105B7A51CDD2F2.png|286]]
>
>
> - **方式二**( 通过 `-L`指定库搜索路径 & `-l` 指定库名):`gcc main.c -o myprogram -L. -lmylib`
>
> ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-A49CF7807ACFA87D150ABC058524ED33.png|283]]
>
> 注:`-l` 指定库名时**需省略前缀 `lib` 以及后缀 `.a` 或`.so`**
>
> [!example] Visual Studio 下使用静态库:
>
> 
>
<br><br><br>
# 动态库 | 共享库
> 动态库被设计于应用在 "**动态链接**" 的场景,但也可被 "**静态链接**"。
Linux 下的**共享库**(也称**动态库**)是 ELF 目标文件的一种,供其他程序**在运行时==链接==**,**其可被加载到任意的内存地址**。
共享库的特点:
1. **动态库==独立==于可执行文件**,由**可执行文件==在运行时通过 "动态链接器" 完成链接和加载==**。
2. **代码共享**:
1. 动态库中的代码和数据不会嵌入到可执行文件中,**所有引用该库的可执行文件链接/共享同一份 `.so` 文件**
2. **动态库的==在内存中只会存在一份 "代码段" 实例==**,**所有引用该共享库的进程==共享动态库的代码段==**,但各自持有一份 **"数据段"** 的副本。
> [!info] **动态库**
>
> - 在 Unix-like 系统(如 Linux )下称为 **==共享库==** 或**共享目标文件**(Shared Objects File),
> - 在 Windows 下称为 **==动态链接库==**(DLL)。
> ^squbw5
> [!NOTE] ❓<font color="#c0504d">为什么需要动态库?</font>
>
> 
>
> 静态库存在如下缺点:
>
> - 会导致空间浪费
> - 由于静态库内嵌于程序中,**若多个程序使用相同的静态库,相当于静态库中代码将在内存中存在多份拷贝**。
> - 对程序的更新、部署和发布不便
> - 如果静态库更新了,**所有使用它的应用程序要使用新的静态库版本,都需要重新编译、发布**。
>
> 使用动态库的优点:动态库不会内嵌于可执行文件,而是在程序运行时才被动态载入,因此:
>
> - **不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例**,规避了空间浪费问题。
> - **当程序需要使用新版本的动态库时,只需替换库文件即可**。
>
<br>
## 动态库的 "部分链接" 与 "完全链接"
- 「**部分链接**」:在链接阶段,**链接器需要==复制动态库中的一些重定位和符号表信息==**,从而**保证程序==在运行时==能够解析对动态库中代码和数据的引用**,此过程为 "部分链接",由 "**链接器**" 完成。
- 「**完全链接**」:程序**在运行时链接到动态库**,称之为 "**完全链接**"。该过程由 "**==动态链接器==**"完成,根据**环境变量 `LD_LIBRARY_PATH`** 指定的路径去**搜索程序所需链接的动态库**。
> [!NOTE] 完全链接的过程
>
> **加载器加载 "部分链接" 的可执行文件**,当发现该程序中**包含一个动态链接器的路径名**后,加载器**加载和运行该 "动态链接器"**,由**动态链接器**完成对动态库的加载和重定位任务(**重定位可执行程序中所有对动态库中符号定义的引用**),此后将控制传递给应用程序。
> 自此,共享库的内存位置固定,且在程序执行的整个过程中不会改变。
<br>
## 动态库的创建
Linux 下 gcc 编译器通过命令 `gcc -shared -fpic` 指定**创建共享库文件**。
- `-shared`:指示生成 "**共享库文件**",而非可执行文件。
- `-fpic`:指示编译器生成 "**位置无关的代码**"
> [!example] 创建共享库:`gcc -shared -fpic -o libvector.so addvec.cpp mulvec.cpp`
>
> ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-F476CD734C4EAFC4E6160BD7102E9D7C.png|447]]
>
![[05-工具/GNU 工具/gcc 编译器#^69vo2q]]
![[02-开发笔记/03-计算机基础/编译与链接/静态库与动态库#^4pmzpt]]
<br>
## 动态库的使用
### Linux gcc/g++ 编译时使用动态库
**在下述链接过程中,链接器仅==复制了动态库中的一些重定位和符号表信息==,使得程序在==运行时能够解析对动态库中代码和数据的引用==,但链接器没有将任何动态库的代码或数据嵌入到可执行文件中。**
- **方式一**(**直接指定动态库文件 j**):`g++ -o prog main.cpp ./libvector.so`
![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-AF6BB3663350F67C3B560F3BC31302E8.png|267]]
![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-0F6EE742A557E4BF84D4426B0083C1DC.png|744]]
- **方式二**( 通过 `-L`指定**链接时**库搜索路径 & `-l` 指定库名 & 通过 `LD_LIBRARY_PATH` 指定**运行时库搜索路径**):
- `gcc main.c -o myprogram -L. -lmylib`
- `export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH`
![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-A49CF7807ACFA87D150ABC058524ED33.png|283]]
![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-867BE2AA01E4835938EB963347F109C2.png|774]]
![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-297EC71AF7DC870EDF425BD452D410EB.png|774]]
> [!NOTE] 方式一与方式二的区别
>
> - 方式一中,直接给出了**动态库的所在路径**,因此**程序运行时,动态链接器==会直接按此路径==加载动态库**。
> - 方式二中,gcc 选项 `-L` 指定的是 **==链接时对 `-l` 所列出的库的搜索路径==**,而不是 "**动态库的运行时加载路径**"。因此,程序运行时将由**动态链接器**去**指定的库搜索路径(`LD_LIBRARY_PATH`)**下查找动态库,以完成链接。
>
<br>
### Windows 下使用动态库
动态链接库的使用需要库的开发者提供生成的 `.lib` 文件和 `.dll` 文件,或者只提供 `.dll` 文件。
**使用时只能使用 `.dll` 中导出的函数**,未导出的函数只能在 `.dll` 内部使用。
Dll 的调用有**显式连接**和**隐式连接**两种:
- **隐式连接**:需要三项—— `.h` 头文件,**动态库的引入库文件 `.lib`**,**动态库本身 `.dll`** ;
- **显式连接**:只需要 `.dll` 文件即可。
**动态库文件**中包含实际代码和数据,而**引入库文件**中包含 **==动态库导出的函数和变量的符号名==**。

> [!NOTE] 关于 **引入库 `.lib` 文件** (可选,显式链接时只需 DLL 即可)
>
> - 编译链接时,**对动态库只需链接该 `.lib` 文件**,**程序运行时据此加载所需的 DLL**。
> - 该文件只在编译时用到,程序发布时只需包含动态库 `.dll` 文件。
###### 动态库的引入库 lib 与静态库 lib 的区别
动态库的引入库与静态库同为 `.lib` 后缀文件,但实质不同。
- 静态库本身包含实际执行代码、符号表等,,编译链接时,静态库中的函数和数据会被复制并**包含在最终生成的可执行文件**中。
- 引入库**只包含了动态库的地址符号表**等,确保程序找到对应函数的基本地址信息,实际执行代码位于动态库 `dll` 中,只在需要调用时才加载。
<br><br>
## 动态库的运行时搜索路径
程序**在运行时链接到动态库**的过程由 "**动态链接器**" 完成,动态链接器会在**环境变量 `LD_LIBRARY_PATH`** 指定的路径下**搜索程序所需链接的动态库**[^5]。
#### 配置动态库的运行时搜索路径
#TODO
<br><br><br>
# 共享库的 "位置无关特性" 与 "延迟绑定"
- 共享库的 "**==位置无关特性==**" ——**共享库==可以被加载到任意的内存地址==中**,**程序中对==共享库中符号引用的实际地址==将在 "运行时" 由动态链接器解析得到**。
- 共享库函数地址的 "**==延迟绑定==**" —— **动态链接器对共享库中==函数运行时地址==的解析会直到程序 "==首次调用==该函数" 时才完成**。
为支持上述两点特性,链接器:
- 引入了 "**==全局偏移量表==(Global Offset Table, ==GOT==)**" 以实现支持共享库的 "**位置无关特性**"。
- 引入了 "**==过程链接表==((Procedure Linkage Table,==PLT==))** " 以实现对共享库函数地址的 "**延迟绑定**"(需结合 GOT 表完成)
> [!note] 未动态链接到动态库时,不需要 GOT 和 PLT 表
>
> 如果程序未使用动态库,不需要动态链接,则链接阶段不会生成这两个表,因为**所有符号的地址在链接时就已经确定,不需要在运行时解析符号**。
#### 查看 GOT 和 PLT 表
![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-80F8B18E48F74367941EB2CC699BADBB.png|654]]
<br>
## 全局偏移量表 GOT
「**全局偏移量表 GOT**」:用于存储**共享中==全局变量和函数==** 的==**运行时内存地址**==,实现**支持共享库的 "位置无关" 特性**。
GOT 表中**每个条目对应一项==共享库中的全局符号==(函数或全局变量)**,条目用以存储 "**该符号在运行时的内存地址**":
- 在链接阶段,程序中 "**对共享库中符号的引用**" 都会重定向为 "**==对该表中对应条目的引用==**",对 GOT 表**条目所指地址**进行**间址访问**。
- 在运行时加载阶段,每个条目**由 "动态链接器" 填入==条目对应符号的运行时内存地址==**。
GOT 是个数组,其中**每个条目占 8 字节**。
> [!NOTE] ❓既然 GOT 表中存了共享库中 "函数" 的地址,为什么还需要 PLT 而不是直接跳转?
>
> 目的在于实现 "**延迟绑定**"。
>
<br>
## 过程链接表 PLT
「**==过程链接表== PLT**」:存储了**进行函数调用的相关指令**,程序中对 "**共享库中函数的符号引用**" 都会重定位为对"**==该表中对应条目的引用==**",该表**结合 GOT 表** 用于实现 **==延迟绑定函数地址==**。
PLT 表是个数组,其中每个条目是 **16 字节的==代码 (指令)==**:
- `PLT[0]` 是特殊条目,**跳转到动态链接器**。
- 其余每个条目**负责调用**一个 "**具体函数**"。
<br>
## 位置无关特性的实现原理
基于 **GOT 表** 和 **PLT 表**,采取以下机制:
- 在**链接**阶段, **链接器** 对 "**使用了共享库的可执行文件**" 采取以下处理:
1. 在其 **"数据段""起始处**创建 "**==全局偏移量表 GOT==**",同时**为 GOT 中每个条目**生成一个对应的 "**重定位记录**",
供**动态链接器**在运行时**填入各条目对应符号的运行时内存地址**
2. 在其 "**代码段**" 中创建 "**==过程链接表 PLT==**"。
3. 将可执行文件中所有对 "**==共享库中全局变量==**" 的符号引用**重定位到 ==GOT 对应条目处==**;
4. 将可执行文件中所有对 "**==共享库中函数==**" 的符号引用**重定位到 ==PLT 对应条目处==**。
- 在**运行时加载**阶段,**动态链接器**完成 **==运行时符号解析==**——对 **GOT 表中的条目进行重定位,==填入符号的运行时内存地址==**。
- 注:对于 "**函数地址**" 的解析会推迟到 "**程序第一次调用该函数时**",即 "**延迟绑定**",需结合 PLT 表完成。
![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-66707AE0A0595D4C91B8536D7125FE0A.png|674]]
> [!important] ❓<font color="#c0504d">GOT 表是如何支持 "位置无关代码" 的?</font>
>
> **GOT 表位于可执行程序的数据段起始处,与符号引用之间的相对距离是固定的**,
> 因此在链接阶段可确定该 "**相对地址**", 从而**将==对 "符号" 的引用==重定位为==对 "GOT 表中条目" 的引用==**。
>
> 而 "**GOT 表中条目**" 存储了**共享库中==全局变量和函数==的==运行时内存地址==**,因此**可供程序在运行时通过 GOT 表获取符号的实际内存地址**。
> [!NOTE] 在链接阶段,所有**对 "共享库中全局符号" 的引用**会被重定位为如下形式
>
> 对共享库中变量的访问:
> 例如 `mov offset(%rip), %rax`,其中`offset(%rip)` 是**该符号在 GOT 表中条目的位置**,采用了 "**==相对地址==**"。
> 在运行时,存入 `%rax` 中的是 "**变量在运行时的内存地址**",因此访问该变量还需一次内存访问:`(%rax)`。
>
> >
>
> 对共享库中函数的访问还需要依赖于 PLT 表,会略微复杂一些。
<br>
## 共享库中函数的 "延迟绑定"
「**延迟绑定**」:直到程序**首次调用共享库中的==函数==** 时才**解析该函数的运行时内存地址**,并**填入到 GOT 表**中,此后**调用则通过 GOT 表直接获取函数地址**。
作用:减少程序启动时的加载时间
<br>
### 延迟绑定的实现原理
> ❓<font color="#c0504d">延迟绑定是如何实现的?</font>
首先,链接阶段会将**对 "共享库中函数" 的符号引用** 重定位为 "**==该函数在 PLT 表中的对应条目==**" ,
即一条函数调用指令 `call func` 的实际过程将是:`call PLT[..]`,其中 **PLT 表中对应条目的首条指令为 `jmp *GOT[...]`**,
即**跳转到==该函数在 GOT 中对应条目所指向的地址处==**。
"**延迟绑定**" 体现在:
每个函数在 GOT 表中的条目**初始值** 为 "**==该函数对应的 PLT 条目中的第二条指令地址==**",
其作用大致为 `call PLT[0]`,即**启动动态链接器来解析函数的运行时内存地址,并将其填入(更新)到 GOT 条目中**。
因此,**当==首次调用函数==时,动态链接器会==将函数的运行时内存地址填入 GOT 中对应条目==**,
而在后续调用中,通过 `call PLT[..]` => `jmpq *GOT[..]` 就会直接完成函数调用。
> [!example] 延迟绑定示例 [^7](P491)
> ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-2CA2681B2717D01B156271FE21B025DA.png|666]]
<br><br><br>
# 参考资料
# Footnotes
[^3]: [C++静态库与动态库的区别?](https://blog.csdn.net/dd_hello/article/details/81782934)
[^4]: [C语言 Windows下使用gcc制作静态库与动态库](https://blog.csdn.net/TIME_LEAF/article/details/115333179)
[^5]: [linux下 GCC编译链接静态库&动态库 ](https://www.cnblogs.com/thechosenone95/p/10605172.html)
[^6]: 《程序员的自我修养:链接、装载与库》
[^7]: 《深入理解计算机系统》