静态库和共享(动态)库

静态库和共享(动态)库

库的介绍

什么是库

库是二进制文件,是源代码文件的另一种表现形式,是加了密的源代码;是喜喜呃功能相近或者是相似的函数的集合体。

使用库有什么好处

  • 提高代码的可重用性,而且还可以提高程序的健壮性;
  • 可以减少开发者的代码开发量,缩短开发周期。

库制作完成后,如何给用户使用

  • 头文件 — 包含了库函数的声明
  • 库文件 — 包含了库函数的代码实现
  • 注意:库不能单独使用,只能作为其他执行程序的额一部分完成某些功能,也就是说只能被其他程序调用才能使用。
  • 库可分静态库(static library)和共享库(shared library)两种。

静态库(static library)

静态库可以认为是一些目标代码的集合,是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。按照习惯,一般以 .a 作为文件后缀名。

静态库的命名一般分为三个部分:

  • 前缀:lib
  • 库名称:自定义即可,如:test
  • 后缀:.a

座椅最终的静态库的名字应该是:libtest.a

静态库的制作

下面以 func1.c、func2.c 和 head.h 三个文件为例讲述静态库的制作和使用,其中 head.h 文件中有函数的声明,func1.c 和 func2.c 文件中有函数的实现。

步骤一:将 c 源文件生成对应的 .o 文件

1
gcc -c func1.c func2.c

步骤二:使用打包工具与 ar 将准备好的 .o 文件打包为 .a 文件

  • 在使用 ar 工具的时候需要添加参数 rcs
    • r:表示将文件插入(replace)到库中
    • c:表示创建(create)库
    • s:表示生成索引(symbol)表
  • 命令: ar rcs 静态库名 .o 文件
    • ar rcs libtest.a func1.o func2.o

创建静态库的过程

静态库的使用

静态库制作完成之后,需要将 .a 文件和头文件一定发布给用户。假设测试文件为 main.c,静态库文件为 libtest.a,头文件为 head.h。

用到的参数:

  • -L:指定要链接的库的所在目录
  • -l:指定链接时需要的静态库,去掉前缀和后缀
  • -I:指定 main.c 文件用到的头文件 head.h 所在的路径
  • gcc -o main main.c -L./ -ltest -I./

静态库的优缺点

优点

  • 库函数最终被打包到应用程序中,实现时函数本地化,寻址方便、速度快。(库函数调用效率 == 自定义函数使用效率)
  • 程序在运行时与函数库再无瓜葛,移植方便。

缺点

  • 消耗系统资源较大,每个进程使用静态库都要复制一份,无端浪费内存。

  • 静态库会给程序更新、部署喝发布带来麻烦。如果静态库 libxxx.a 更新了,所有使用它的程序都需要重新编译、发布给用户(对于用户来说,可能是一个很小的改动,却导致整个程序重新下载)。

共享库(shared library)/动态库

共享库在程序编译时并不会被链接到目标代码中,二十在程序运行时才被载入。不容的应用如果调用相同的库,那么在内存里只需要有一份该共享库的拷贝,规避了空间浪费问题。动态库在程序运行时才被载入,也解决了静态库对程序的更行、部署和发布带来的麻烦。用户只需要更新动态库即可,增量更新。为什么需要动态库,其实也是静态库的特点导致的。

按照习惯,一般以 .so 作为文件后缀名。共享库的命名一般分为三个部分:

  • 前缀:lib
  • 库名称:自定义即可,如:test
  • 后缀:.so

所以最终的动态库名称应该为:libtest.so

共享库的制作

  1. 生成目标文件 .o,此时要加编译选项:-fPIC(fpic)’

    gcc -fpic -c func1.c func2.c

    参数:-fpic 创建于地址无关的编译程序(pic,position independent code),目的就是为了能够在多个应用程序间共享。

  2. 生成共享库,此时要加链接器选项:-shared(指定生成动态链接库)

    gcc -shared func1.o func2.o -o libtest.so

共享库的使用

引用动态库编译成可执行文件(跟静态库方式一样):

用到的参数:

  • -L:指定要链接的库的所在目录
  • -l:指定链接时需要的动态库,去掉前缀和后缀
  • -I:指定 main.c 文件用到的头文件 head.h 所在的路径
  • gcc -o main main.c -L./ -ltest -I./

然后运行:./main,发现竟然报错了。

分析为什么在执行的时候找不到 libtest.so 库

  • 当系统加载可执行代码的时候,能够知道其所依赖库的名字,但是还需要知道所依赖的库的绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。

    ldd 命令可以查看可执行文件依赖的库文件,执行 ldd main,就可发现 libtest.so 找不到

  • 对于 elf 格式的可执行文件,是由 ld-linux.so* 来完成的,它先后搜索 elf 文件的 DT_RPATH 段 – 环境变量 LD_LIBRARY_PATH – /etc/ld.so.cache 文件列表 – /lib/,/usr/lib/ 目录找到库文件后将其载入内存。

    使用 file 命令可以查看文件的类型:file main

如何让系统找到共享库

  • 拷贝自己制作的共享库到 /lib 或者 /usr/lib
  • 临时设置 LD_LIBRARY_PATH:
    • export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
  • 永久设置,把 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径 设置到 ~/.bashrc 文件中,然后在执行下列三种办法之一:
    • 执行 . ~/.bashrc 使配置文件生效
    • 执行 source ~/.bashrc 使配置文件生效
    • 退出当前终端,然后再次登录也可以使配置文件生效
  • 永久设置,把 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径 设置到 /etc/profile 文件中。
  • 将其添加到 /etc/ld.so.cache 文件中
    • 编辑 /etc/ld.so.conf 文件,加入库文件所在的目录的路径
    • 运行 sudo ldconfig -v,该指令会重建 /etc/ld.so.cache 文件

解决了库的路径问题之后,再次 ldd 命令可以查看可执行文件依赖的库文件,ldd main

共享库的特点

  • 动态库把对一些库函数的链接载入推迟到程序运行的时期。
  • 可以实现进程之间的资源共享(因此动态库也称为共享库)。
  • 将一些程序升级变得简单。
  • 甚至可以真正做到链接载入完全由程序员在代码中控制(显式调用)。

比较静态库和动态库的优缺点

静态库的有点

  1. 执行速度快,是因为静态库已经编译到可执行文件内部了。
  2. 移植方便,不依赖于其他的库文件

静态库的缺点

  1. 耗费内存,是由于每一个静态库的可执行程序都会加载一次。
  2. 部署更新麻烦,因为静态库修改以后所有的调用到这个静态库的可执行文件都需要重新编译。

动态库的优点

  1. 节省内存。
  2. 部署升级更新方便,只需替换动态库即可,然后再重启服务。

动态库的缺点

  1. 加载速度比静态库慢。
  2. 移植性差,需要把所有用到的动态库都移植。

由于由静态库生成的可执行文件是把静态库加载到了其内部,所以静态库生成的可执行文件一般会比动态库大。


静态库和共享(动态)库
https://irisislove.github.io/2025/03/04/lib-and-so/
作者
Iris
发布于
2025年3月4日
许可协议