静态库和动态库详解

1.什么是库

库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。

库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行。

库文件有两种,静态库和动态库(共享库),区别是:

  • 静态库在程序的链接阶段被复制到了程序中;
  • 动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。

库的好处:

  1. 代码保密
  2. 方便部署和分发

2.静态库的制作

静态库的命名规则:

  • Linux:libxxx.a
  • Windows:libxxx.lib

静态库的制作:

  • gcc获得 .o 文件
  • 使用 ar 工具(archive)将 .o 文件打包
    • r - 将文件插入备存文件中
    • c - 建立备存文件
    • s - 索引
1
2
3
//add.c, sub.c, mult.c, div.c
gcc -c add.c #获得add.o, sub.o, mult.o, div.o
ar rcs libcalc.a add.o, sub.o, mult.o, div.o # 获得静态库libxxx.a

静态库的使用

链接时须注意头文件的展开和库文件的加载,另外还需注意目录层级。

1
2
3
4
5
6
7
8
9
10
11
12
$ tree
.
├── include
│   └── head.h
├── lib
│   └── libcalc.a
├── main.c
└── src
├── add.c
├── div.c
├── mult.c
└── sub.c

错误的做法:

image-20231020165111778

image-20231020165149212

正确的做法:

1
2
3
4
5
6
7
8
# -I指定头目录,-l指定库名称,-L指定库路径
$ gcc main.c -o app -Iinclude -lcalc -Llib
$ ./app #正确运行
a = 20, b = 12
a + b = 32
a - b = 8
a * b = 240
a / b = 1.666667

3.动态库的制作

动态库的命名规则

  • Linux:libxxx.so (Linux下是一个可执行文件)
  • Windows:libxxx.dll

动态库的制作

  1. gcc 获得 .o 文件,得到和位置无关的代码
  2. gcc 获得动态库

-fpic 用于汇编阶段,产生的目标文件没有绝对地址,全部用相对地址,这正好满足了共享库的要求,共享库被加载时地址不是固定的。如果不加 -fpic ,那么生成的目标文件就会与位置有关。

1
2
$ gcc -c -fpic add.c sub.c div.c mult.c //生成.o文件
$ gcc -shared *.o -o libcalc.so

使用动态库

1
2
3
4
5
6
7
$ gcc main.c -o dynamic_app -Iinclude -L../calc -lcalc
$ ./dynamic_app
a = 20, b = 12
a + b = 32
a - b = 8
a * b = 240
a / b = 1.666667

TODO:实测加不加-fpic没什么区别,都是位置无关。

4.静态库和动态库的工作原理

  • 静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中
  • 动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中
  • 程序启动之后,动态库会被动态加载到内存中,通过 ldd (list dynamic dependencies)命令检查动态库依赖关系
  • 如何定位共享库文件呢?
    • 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路 径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是 由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 ——> 环境变量 LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib//usr/lib 目录找到库文件后将其载入内存。

如何配置共享库文件?

  • 临时配置(切换shell失效)
1
2
3
4
5
6
7
8
# 处理生成app文件(app非必定名字)
gcc main.c -o app -I ./include/ -l calculate -L ./lib/

# 配置环境变量,值为lib文件的绝对路径(lib文件夹下用pwd指令输出绝对路径)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:lib文件的绝对路径

# 在处理后生成的app文件下用lld指令,如果libcalc.so已分配内存且路径在lib文件夹下则链接成功
ldd app
  • 永久配置1(用户级)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 处理生成app文件(app非必定名字)
gcc main.c -o app -I ./include/ -l calculate -L ./lib/

# 切入home/{current_user}文件下找到.bashrc文件修改
vim ~/.bashrc

# 在.bashrc文件最后一行插入以下指令
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:lib文件的绝对路径

# 生效更新
. .bashrc # source .bashrc

# 在处理后生成的app文件下用lld指令,如果libcalc.so已分配内存且路径在lib文件夹下则链接成功
ldd app
  • 永久配置2(系统级)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 处理生成app文件(app非必定名字)
gcc main.c -o app -I ./include/ -l calculate -L ./lib/

# 用管理员身份进入系统变量设置文件
sudo vim /etc/profile

# 在profile文件最后一行插入以下指令
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:lib文件的绝对路径

# 生效更新
. /etc/profile # source /etc/profile

# 在处理后生成的app文件下用lld指令,如果libcalc.so已分配内存且路径在lib文件夹下则链接成功
ldd app
  • 永久配置3(系统级,便捷)
1
2
3
4
5
6
7
8
9
10
11
# 处理生成app文件(app非必定名字)
gcc main.c -o app -I ./include/ -l calculate -L ./lib/

# 由于/etc/ld.so.cache文件是二进制文件,所以我们间接修改/etc/ld.so.conf文件:插入lib文件的绝对路径保存即可
sudo vim /etc/ld.so.conf

# 生效更新
sudo ldconfig

# 在处理后生成的app文件下用lld指令,如果libcalc.so已分配内存且路径在lib文件夹下则链接成功
ldd app

5.静态库和动态库的区别

静态库、动态库区别来自链接阶段如何处理链接成可执行程序。分为静态链接方式和动态链接方式。

静态库优缺点

image-20231020185950156

  • 优点

    • 静态库被打包到应用程序中加载速度快。
    • 发布程序无需额外提供静态库,移殖方便。
  • 缺点

  • 消耗系统资源,浪费内存。

  • 更新、部署、发布麻烦。

动态库优缺点

image-20231020185903567

  • 优点
    • 可以实现进程间资源共享(共享库)。
    • 更新、部署、发布简单。
    • 加载动态库时间可控。
  • 缺点
    • 加载速度相对于静态库慢。
    • 发布程序时需要提供依赖的动态库。