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


点击上方
蓝字

关注我们

下面开始今天的学习~

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


面试中经常会问到以下问题:

  • 怎么创建一个动态库?
  • 动态库文件的后缀名是什么?
  • 怎么使用一个动态库?
  • 动态库的命名规范?
  • 系统默认的动态库的查找路径?
  • 动态库显示连接所使用的系统库是什么?



什么是库


库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
库有两种:

  • 静态库(.a、.lib)

  • 动态库(.so、.dll)


在一个程序的编译过程中,分为以下几个步骤: 预处理
编译
汇编
链接
。本文中讨论的链接库就是针对最后一个步骤「链接」而言的。

动态库和静态库的区别

左图为静态链接库,右图为动态链接库


对于静态链接库而言在链接阶段,会将汇编生成的「目标文件.o」与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接:

  • 静态链接库对函数库的链接是放在编译时期完成的。程序在运行时与函数库就没有了任何的联系。

  • 它比较浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

  • 静态库对程序的更新和发布也会带来麻烦。如果静态库更新了,所有使用它的应用程序都需要重新编译、部署、发布给用户。

静态链接可以理解为最后生成了一个「单文件免安装绿色版」的程序,优点在于移植的时候只需要移动这一个文件,缺点在于文件体积非常大,为了解决这样的问题,就有了动态链接库。

动态链接库在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入。

  • 不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,可以实现进程之间的资源共享。(因此动态库也称为共享库)规避了空间浪费问题。

  • 动态库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布带来的麻烦。用户只需要更新动态库即可将一些程序升级变得简单,增量更新。

动态库连接到系统空间,如果多个程序连接了同一个库,那么只需要一份,优点在于编译程序的时候不会将对应的库文件全部打包在生成的程序中,而是保留了到对应库的链接,缺点就是移植的时候如果只移动了对应的程序没有安装相关的库的话,就会看到类似以下喜闻乐见的结果了。


在 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
中文件内容如下:

int max(int a, int b, int c)

{

   int max = ( a < b ) ? b : a;

   return ( ( max < c ) ? c : max );

}

可以通过:

gcc -fPIC -shared -o libmax.so max.c



将其编译为共享库,


-fPIC
是编译选项, PIC
是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性; -shared
是链接选项,告诉 gcc 生成动态库而不是可执行文件。


为了让用户知道我们的动态库中有哪些接口可用,我们需要编写对应的头文件,比如可以写一个 max.h

#ifndef __MAX_H__

#define __MAX_H__


int max(int a, int b, int c);
#endif

设置一个驱动函数来测试我们编写的动态库:

#include 

#include "max.h"


int main(int argc, char *argv[]) { int a = 12, b = -2, c = 120; printf("The max value of 12, -2 and 120 is %d.\n", max(a, b, c)); return 0; }

通过 gcc test.c -L. -lmax
来生成 a.out,其中 -lmax
表示要链接 libmax.so
-L.
表示搜索要链接的库文件时包含当前路径。

同一目录下同时存在同名的动态库和静态库,比如 libmax.so
libmax.a
都在当前路径下,则 gcc 会优先链接动态库。

但是这样直接运行的话,会出现一个错误:

./a.out: error while loading shared libraries: libmax.so: cannot open shared object file: No such file or directory

由于 Linux 是通过 /etc/ld.so.cache
文件搜寻要链接的动态库的,而 /etc/ld.so.cache
是 ldconfig 程序读取 /etc/ld.so.conf
文件生成的,本次使用的动态库 libmax.so
并不在对应的目录下,就会导致程序无法找到对应的动态链接库,这样我们的解决方法有二:


  • 如果仅仅是本地使用,可以在编译后指定一个环境变量: LD_LIBRARY_PATH=. ./a.out
    ,这样程序会在本地寻找


  • 如果需要在系统层面共享这个库,可以把 libmax.so
    所在的路径添加到 /etc/ld.so.conf
    中,再以 root 权限运行 ldconfig
    程序,更新 /etc/ld.so.cache

具体采用的方法因使用场景而异,如果仅仅是测试用途的话,可以直接使用添加环境变量的方式解决。


小结

动态链接库是各个系统中的一个重要的组成部分且在 Linux 开发相关领域中尤为重要,也是一个面试的高频考点,除了动态链接库以外,还有以下相关知识也是高频考点,在面试前一定要准备好:

  • Linux 下 make 与 makefile。用什么参数指定 makefile 文件?什么是默认的makefile 文件?

  • Linux 静态库的使用,怎么创建一个静态库?怎么使用一个静态库?静态库文件的后缀名是什么?静态库的命名规范?


本文作者:Nova Kwok
编辑&版式:霍霍
声明:本文归 “力扣” 版权所有,如需转载请联系。
文中部分图片来源于网络,为非商业用途使用,如有侵权联系删除。
推荐阅读


力扣专属福利

点个在看,少个 bug

:point_down: