动态链接库|面试 | Linux 下的动态链接库问题


动态链接库|面试 | Linux 下的动态链接库问题文章插图
在 Linux 开发时 , 我们经常会看到一些形如 xxx.so 的名称出现 , 其中 so 是 Shared Object 的缩写 , 即可以共享的目标文件 , 也就是我们所称为的动态链接库 , 和在 Windows 下大家玩游戏时遇到的 xxx.dll 错误中的文件是一个类型的 。
动态链接库|面试 | Linux 下的动态链接库问题文章插图
面试中经常会问到以下问题:

  • 怎么创建一个动态库?
  • 动态库文件的后缀名是什么?
  • 怎么使用一个动态库?
  • 动态库的命名规范?
  • 系统默认的动态库的查找路径?
  • 动态库显示连接所使用的系统库是什么?
一、什么是库库是写好的现有的 , 成熟的 , 可以复用的代码 。 现实中每个程序都要依赖很多基础的底层库 , 不可能每个人的代码都从零开始 , 因此库的存在意义非同寻常 。 本质上来说库是一种可执行代码的二进制形式 , 可以被操作系统载入内存执行 。
库有两种:
  • 静态库(.a、.lib)
  • 动态库(.so、.dll)
在一个程序的编译过程中 , 分为以下几个步骤:预处理 , 编译 , 汇编 , 链接 。 本文中讨论的链接库就是针对最后一个步骤「链接」而言的 。
动态链接库|面试 | Linux 下的动态链接库问题文章插图
动态库和静态库的区别
左图为静态链接库 , 右图为动态链接库
动态链接库|面试 | Linux 下的动态链接库问题文章插图
对于静态链接库而言在链接阶段 , 会将汇编生成的「目标文件.o」与引用到的库一起链接打包到可执行文件中 。 因此对应的链接方式称为静态链接:
  • 静态链接库对函数库的链接是放在编译时期完成的 。 程序在运行时与函数库就没有了任何的联系 。
  • 它比较浪费空间和资源 , 因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件 。
  • 静态库对程序的更新和发布也会带来麻烦 。 如果静态库更新了 , 所有使用它的应用程序都需要重新编译、部署、发布给用户 。
静态链接可以理解为最后生成了一个「单文件免安装绿色版」的程序 , 优点在于移植的时候只需要移动这一个文件 , 缺点在于文件体积非常大 , 为了解决这样的问题 , 就有了动态链接库 。 动态链接库在程序编译时并不会被连接到目标代码中 , 而是在程序运行时才被载入 。
  • 不同的应用程序如果调用相同的库 , 那么在内存里只需要有一份该共享库的实例 , 可以实现进程之间的资源共享 。 (因此动态库也称为共享库)规避了空间浪费问题 。
  • 动态库在程序运行时才被载入 , 也解决了静态库对程序的更新、部署和发布带来的麻烦 。 用户只需要更新动态库即可将一些程序升级变得简单 , 增量更新 。
动态库连接到系统空间 , 如果多个程序连接了同一个库 , 那么只需要一份 , 优点在于编译程序的时候不会将对应的库文件全部打包在生成的程序中 , 而是保留了到对应库的链接 , 缺点就是移植的时候如果只移动了对应的程序没有安装相关的库的话 , 就会看到类似以下喜闻乐见的结果了 。
动态链接库|面试 | Linux 下的动态链接库问题文章插图
在 Linux 下一个动态库有y三个不同名字的文件组成:
  • soname 文件
  • lib + 链接库名字 + .so + .版本号
  • real name 文件
  • lib + 链接库名字 + .so + .版本号.次版本号.发行号
  • linker name 文件
  • lib + 链接库名字 + .so
当程序在内部列出所需要的链接库时 , 仅仅使用 soname 。 当你创建一个链接库时 , 使用 real name 。 安装一个新的链接库时 , 把它复制到一个DLL文件夹里 , 然后运行程序 ldconfig 。 ldconfig 检查存在的 real name 文件 , 并且创建指向它符号链接 soname 文件 。 可能大家比较常见到的有 libsodium 等 。
二、创建一个动态库有了上面关于库的一些基础知识之后 , 我们可以开始尝试创建一个动态库来供程序使用了 。
比如我们有一个求最大值的函数 max(int a,int b,int c), 放在文件 max.c 中文件内容如下:
动态链接库|面试 | Linux 下的动态链接库问题文章插图
可以通过:
动态链接库|面试 | Linux 下的动态链接库问题文章插图
将其编译为共享库 , -fPIC是编译选项 , PIC是 Position Independent Code 的缩写 , 表示要生成位置无关的代码 , 这是动态库需要的特性; -shared是链接选项 , 告诉 gcc 生成动态库而不是可执行文件 。 为了让用户知道我们的动态库中有哪些接口可用 , 我们需要编写对应的头文件 , 比如可以写一个 max.h :