🎬 胖咕噜的稞达鸭 :个人主页
🔥 个人专栏 : 《数据结构》《C++初阶高阶》
《Linux系统学习》
《算法日记》
⛺️技术的杠杆,撬动整个世界!


什么是库?
库是写好的可以复用的代码,依赖库可以实现某些代码。
静态库:.a[Linux] .lib[windows]
动态库:.so[Linux] .dll[windows]
有时候电脑的杀毒软件会杀掉动态库。
静态库的制作
静态库静态库在程序编译链接时把库的代码链接到可执行文件中,程序运行时不再需要静态库。
- 动静态库中,要不要包含main函数呢?NO!不要
- 头文件包含了方法的声明,
.c文件包含了方法的实现,原来所有的库(无论是动态还是静态,都是源文件对应的.o文件)!! - 静态库本质上就是很多个
.o文件打包形成的。
特点:
-
生成的可执行文件体积较大
-
运行时不需要外部依赖
-
库更新需要重新编译链接整个程序
生成静态库:
makefilelibmystdio.a: my_stdio.o my_string.o
ar -rc @ ^
使用静态库:
cpp
# 不同场景下的使用方式
gcc main.c -lmystdio # 系统路径下
gcc main.c -L. -lmymath # 当前目录下
gcc main.c -I头文件路径 -L库文件路径 -lmymath # 自定义路径
cpp
[keda@VM-0-4-centos my_stdio]$ ar -rc libmyc.a *.o
[keda@VM-0-4-centos my_stdio]$ ll
total 44
-rwxrwxr-x 1 keda keda 13224 Jan 1 20:21 code
-rw-rw-r-- 1 keda keda 74 Jan 1 14:55 Makefile
-rw-rw-r-- 1 keda keda 3170 Jan 4 15:50 mylib.a
-rw-rw-r-- 1 keda keda 1593 Jan 4 15:49 mystdio.c
-rw-rw-r-- 1 keda keda 505 Jan 3 17:20 mystdio.h
-rw-rw-r-- 1 keda keda 0 Jan 4 15:48 mystring.c
-rw-rw-r-- 1 keda keda 0 Jan 4 15:48 mystring.h
-rw-rw-r-- 1 keda keda 944 Jan 4 15:49 mystring.o
-rw-rw-r-- 1 keda keda 412 Jan 4 15:46 usercode.c
-rw-rw-r-- 1 keda keda 2024 Jan 4 15:49 usercode.o
.a静态库,本质是一种归档文件,不需要使用者解包,而是用gcc/g++直接进行连接即可。
ar -rc libmyc.a *.o
| replace and create
archive
静态库命名规则:lib开头,.a结尾
如果我们要连接这个静态库:gcc -o usercode usercode.o -llibmyc.a
cpp
[keda@VM-0-4-centos my_stdio]$ gcc -o usercode usercode.o -llibmyc.a
/usr/bin/ld: cannot find -llibmyc.a
collect2: error: ld returned 1 exit status
[keda@VM-0-4-centos my_stdio]$ gcc -o usercode usercode.o -L. -lmyc
-L:去哪里找;-lmyc:找什么库
gcc -o usercode usercode.c -I ./lib/include/ -L ./lib/mylib/ -l myc
gcc -I可以指定头文件。
gcc -L指定库目录.
动态库
动态库在程序运行时才链接库的代码,多个程序可以共享使用库的代码。
特点:
- 生成的可执行文件体积较小
- 运行时需要库文件存在
- 库更新无需重新编译程序
- 内存中只需一份副本,可被多个进程共享
cpp
[keda@VM-0-4-centos my_stdio]$ gcc -shared -o libmyc.so *.o
[keda@VM-0-4-centos my_stdio]$ ll
total 56
-rwxrwxr-x 1 keda keda 13224 Jan 5 14:21 code
drwxrwxr-x 4 keda keda 4096 Jan 5 14:28 lib
-rwxrwxr-x 1 keda keda 8304 Jan 5 14:39 libmyc.so
-rw-rw-r-- 1 keda keda 302 Jan 5 14:27 makefile
-rw-rw-r-- 1 keda keda 1593 Jan 4 15:49 mystdio.c
-rw-rw-r-- 1 keda keda 505 Jan 3 17:20 mystdio.h
-rw-rw-r-- 1 keda keda 0 Jan 4 15:48 mystring.c
-rw-rw-r-- 1 keda keda 0 Jan 4 15:48 mystring.h
-rw-rw-r-- 1 keda keda 944 Jan 5 14:37 mystring.o
-rw-rw-r-- 1 keda keda 412 Jan 4 15:46 usercode.c
-rw-rw-r-- 1 keda keda 2080 Jan 5 14:37 usercode.o
[keda@VM-0-4-centos my_stdio]$ file libmyc.so
libmyc.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=94299a5a479b0a7084fad7cba886dba742c0367e, not stripped
libmyc.a:mystdio.o mystring.o
2 ar -rc $@ $^
3 mystdio.o:mystdio.c
4 gcc -c $<
5 mystring.o:mystring.c
6 gcc -c $<
7
8 .PHONY:output
9 output:
10 mkdir -p lib/include
11 mkdir -p lib/mylib
12 cp -f *.h lib/include
13 cp -f *.a lib/mylib
14 tar czf lib.tgz lib
15
16 .PHONY:clean
17 clean:
18 rm -rf *.o libmyc.a lib lib.tgz
操作系统在运行的时候也会寻找动态库:
[keda@VM-0-4-centos my_stdio]$ echo $LD_LIBRARY_PATH
:/home/keda/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
动态库的路径搜索问题:
当程序依赖动态库时,需要确保系统能找到这些库文件。常见解决方案:
- 拷贝到系统路径 :
/usr/lib、/usr/local/lib、/lib64 - 建立软连接
- 设置环境变量 :
export LD_LIBRARY_PATH=/path/to/libs - ldconfig配置 :在
/etc/ld.so.conf.d/中添加路径,执行ldconfig
结论:
- g++/gcc默认使用的是动态库
- 如果一定要使用静态链接,要使用-static,一旦-static。就必须存在对应的静态库
- 如果只存在静态库,可以执行程序,对于该库,就只能静态链接了。
结论2:
在Linux系统下,默认情况安装的大部分库默认都优先安装动态库。
结论3:库:应用程序 = 1 : n
结论4:vs不仅形成可执行程序,也能形成动静态库。
ELF文件格式:理解编译链接的关键
什么是ELF文件?
ELF(Executable and Linkable Format)是Linux下的可执行文件、目标文件、共享库的标准格式。
ELF文件的四种类型:
- 可重定位文件 (
.o文件):包含代码和数据,适合链接 - 可执行文件:可直接执行的程序
- 共享目标文件 (
.so文件):动态链接库 - 内核转储文件:进程执行上下文
ELF文件结构
一个ELF文件由四部分组成:
┌─────────────────┐
│ ELF Header │ ← 描述文件特性,定位其他部分
├─────────────────┤
│ Program Headers │ ← 列举所有有效的段及其属性
├─────────────────┤
│ Sections │ ← 基本组成单位,存储特定类型数据
├─────────────────┤
│ Section Headers │ ← 包含对节的描述
└─────────────────┘
常见的重要节(Sections)
- .text:代码节,存储可执行指令
- .data:数据节,存储已初始化的全局变量和静态变量
- .rodata:只读数据节
- .bss:未初始化的全局变量和静态变量预留位置
- .symtab:符号表,记录函数名、变量名和代码的对应关系
- .got:全局偏移表,支持动态链接的关键数据结构
两个重要视图
-
链接视图(Linking View)- 对应节头表
- 将文件按功能模块划分
- 静态链接时关注此视图
-
执行视图(Execution View)- 对应程序头表
- 告诉操作系统如何加载可执行文件
- 运行加载时使用此视图
从源码到可执行程序的过程
编译阶段
// hello.c
#include<stdio.h>
void run();
int main() {
printf("hello world!\n");
run();
return 0;
}
编译生成目标文件:
gcc -c hello.c # 生成 hello.o
gcc -c code.c # 生成 code.o
目标文件的特点
目标文件(.o文件)是不完整的:
- 函数调用地址暂时设为0
- 包含重定位表,记录需要修正的地址
- 通过符号表记录未定义的符号
链接过程
链接器将多个目标文件合并:
- 合并相同类型的节(如将所有
.text节合并) - 修正地址,填充函数调用的真实地址
- 生成最终的可执行文件
静态链接 :将所有用到的库代码合并到可执行文件中
动态链接:在运行时加载和链接库代码
动态链接的工作原理
GOT和PLT机制
由于代码段(.text)是只读的,不能直接修改函数调用地址。动态链接采用以下机制:
- GOT(全局偏移表) :位于
.data段(可读写),存储函数和变量的实际地址 - PLT(过程链接表):实现延迟绑定,在函数第一次被调用时解析地址
动态链接过程
- 程序启动时,动态链接器(如
ld-linux.so)加载所需库 - 为每个库确定加载地址
- 填充GOT表,记录库中函数的实际地址
- 程序调用函数时,通过PLT→GOT→实际函数的路径执行

这是在描述:多个目标文件(.o文件)在链接时合并成最终可执行程序(main)的过程
多个独立的目标文件
图中显示了四个目标文件,每个都有相同的ELF结构:
ELF Header:每个文件都有自己的头部信息
Program Header Table:程序头表(可选,目标文件可能没有)
Sections(节):包括.text(代码节)、.data(数据节)等
Section Header Table:节头表,描述各个节的信息
合并后的可执行文件
main的ELF格式:最终生成的可执行程序
各个目标文件的同名节被合并:
所有.text节合并成新的.text节
所有.data节合并成新的.data节
其他节也类似合并
一个ELF的构成
ELF⽂件由以下四部分组成:
• ELF头(ELF header) :描述文件的主要特性。其位于⽂件的开始位置,它的主要⽬的是定位文件的其他部分。
• 程序头表(Program header table) :列举了所有有效的段(segments)和他们的属性。表里记着每个段的开始的位置和位移(offset)、长度,毕竟这些段,都是紧密的放在⼆进制文中,需要段表的描述信息,才能把他们每个段分割开。
• 节头表(Section header table) :包含对节(sections)的描述。
• 节(Section ):ELF文件中的基本组成单位,包含了特定类型的数据。ELF文件的各种信息和数据都存储在不同的节中,如代码节存储了可执行代码,数据节存储了全局变量和静态数据等。
这个过程的关键点
- 静态链接的本质
链接器将多个目标文件的相同类型的节合并
例如:code1.o的.text + code2.o的.text + ... = main的.text - 为什么需要合并?
减少页面碎片,提高内存的使用效率,占用的空间少一点;
此外操作系统在加载程序的时候,会将具有相同属性的section合并成一个大的segment,这样就可以实现不同的访问权限,从而优化内存管理和权限访问控制。
举例子:
权限管理问题 :.text(可执行)、.rodata(只读)→ 合并成只读可执行段.data(可读写)、.bss(可读写)→ 合并成可读写段- 系统只需设置2种权限,而不是为每个section单独设置
问题:
问题一:静态库是如何形成可执行程序的?
ELF文件为两个不同的使用者提供了两套说明书:一套给链接器(怎么做链接),一套给操作系统(怎么做加载)。
链接视图(节头表)
给谁用 :链接器(如 ld)
看什么 :readelf -S hello.o
内容:
.text(代码节).data(数据节).rodata(只读数据节).symtab(符号表)- ...等几十个节
为什么需要这个视图?
链接器需要知道:"这个.o文件有哪些代码(.text)、哪些数据(.data)、哪些符号(.symtab)",才能正确地把多个.o文件拼起来。
执行视图(程序头表)
给谁用 :操作系统加载器
看什么 :readelf -l a.out
内容:
- LOAD Segment 1:可执行(.text + .init + ...)
- LOAD Segment 2:可读写(.data + .bss + .got + ...)
为什么需要这个视图?
操作系统需要知道:"这个程序应该加载到内存哪里?哪些可以执行?哪些可以读写?"才能安全高效地加载程序。
[keda@VM-0-4-centos lesson20]$ readelf -h /usr/bin/ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x404324
Start of program headers: 64 (bytes into file)
Start of section headers: 115688 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29
静态链接就是把库中的.o文件都进行合并,并进行统一的编址,链接的时候会修改.o中没有确定的函数地址,在合并完成之后,进行相关call地址,完成代码的调用。
详细解释:文件在编译的时候,查看它的反汇编会发现其地址为全0,链接的时候将相同属性的数据节进行合并(也就是将这个程序需要的所有文件链接在一起),并将地址修改了。
问题二:ELF程序是如何 加载到内存的,(找到它,路径+文件名),ELF程序是如何转化为进程的(逻辑地址,物理地址,虚拟地址),虚拟地址空间?
一个可执行程序,如果没有加载到内存中,该可执行程序,有没有地址?
YOU!有的!
对可执行程序,完成在磁盘上的编址,所有的可执行程序,就是一个seg,所有的seg所有函数,变量编址起始偏移量都从0开始。
虚拟地址空间:不仅仅是进程看代内存的方式,磁盘上的可执行程序,代码和数据编址其实就是虚拟地址的统一编址。操作系统支持,编译器也要支持。
cpu怎么知道,你的可执行程序的起始地址是什么?也就是说CPU怎么知道从哪里 开始执行的呢
进入到CPU中的地址全部都是虚拟地址!

CPU执行流程
步骤1:操作系统告诉CPU
// 进程创建时,操作系统设置:
PCB.entry_point = 0x1060; // 程序入口地址
CPU.PC = 0x1060; // 设置程序计数器
步骤2:CPU获取指令
CPU: "我要执行0x1060处的指令"
MMU: "0x1060是虚拟地址,我来查页表..."
MMU: "对应物理地址是0x12345678"
CPU: "好,我从0x12345678取指令执行"
实用命令总结
查看文件信息
file a.out # 查看文件类型
readelf -h a.out # 查看ELF头
readelf -l a.out # 查看程序头表
readelf -S a.out # 查看节头表
objdump -d a.out # 反汇编代码段
查看依赖关系
ldd a.out # 查看动态库依赖
nm a.out # 查看符号表
库操作
ar -t libxxx.a # 列出静态库内容
ar -rc libxxx.a *.o # 创建静态库
gcc -shared -fPIC -o libxxx.so *.o # 创建动态库