静态库和共享库

程序库是一种包含二进制可执行代码供其他程序引用的文件,Linux中库分为静态库和共享库(动态链接库)。

静态库

本节继续使用上一章节写过的一段例子代码stack.cmain.c。我们这里将stack.c打包成静态库,供main.c引用。

gcc -c stack.c #编译
ar rs libstack.a stack.o #打包
gcc main.c -L. -lstack -Istack -o main #编译main.c和静态库链接到一起

注:库文件名都是以lib开头的,静态库以.a作为后缀,表示Archive。

ar命令

  • -r表示将后面的文件列表添加到文件包,如果文件包不存在就创建它,如果文件包中已有同名文件就替换成新的。
  • -s是专用于生成静态库的,表示为静态库创建索引,这个索引被链接器使用。

gcc命令

  • -c:编译不链接。
  • -L:告诉编译器去哪里找需要的库文件。即使库文件就在当前目录,编译器默认也不会去找的,所以-L.选项不能少。
  • -l-lstack告诉编译器要链接libstack静态库。
  • -I:去哪里找头文件。

编译器默认会找的目录可以用-print-search-dirs选项查看:

共享库

还是之前的例子,我们这里将stack.c编译为共享库。

gcc -c -fPIC stack.c
gcc -shared -o libstack.so stack.o
gcc main.c -g -L. -lstack -Istack -o main

-fPIC:生成位置无关的代码(Position Independent Code)。正常编译,二进制代码的汇编地址是指定的,动态库需要动态加载,运行时内存地址应该根据程序上下文动态指定,所以要以PIC形式编译。

虽然编译通过,但其实是直接运行还是找不到动态库的:

我们使用ldd指令可以查看可执行程序需要的库:

显然,我们的可执行程序是没有找到libstack.so

注:这里除了libstack.so,还有一些其它的库。/lib64/ld-linux-x86-64.so.2是动态链接器,它的路径是在编译链接时指定的,libc.so.6的路径是由动态链接器ld-linux.so.2在做动态链接时搜索到的,linux-vdso.so.1这个共享库其实并不存在于文件系统中,它是由内核虚拟出来的共享库,所以它没有对应的路径,它负责处理系统调用。

一种运行的方法是在环境变量LD_LIBRARY_PATH中,指定我们的libstack.so

共享库的搜索路径

Linux系统中,共享库默认从以下几个地方加载:

  • 环境变量LD_LIBRARY_PATH
  • 缓存文件/etc/ld.so.cache,这个缓存文件由ldconfig命令读取配置文件/etc/ld.so.conf之后生成
  • 默认的系统库路径,如/usr/lib/lib

共享库的命名

真正的系统共享库包含符号链接,每个共享库有三个文件名:real nameso namelinker name

real name:真正的库文件(而不是符号链接)的名字是real name,包含完整的共享库版本号。例如libcap.so.1.10libc-2.8.90.so等。

so name:是一个符号链接的名字,只包含共享库的主版本号,主版本号一致即可保证库函数的接口一致,因此应用程序的.dynamic段只记录共享库的so name,只要so name一致,这个共享库就可以用。

例如libcap.so.1libcap.so.2是两个主版本号不同的libcap库,有些应用程序依赖于libcap.so.1,有些应用程序依赖于libcap.so.2,但对于依赖libcap.so.1的应用程序来说,真正的库文件不管是libcap.so.1.10还是libcap.so.1.11都可以用,所以使用共享库可以很方便地升级库文件而不需要重新编译应用程序,这是静态库所没有的优点。注意libc的版本编号有一点特殊,libc-2.8.90.so的主版本号是6而不是22.8,这是一些特殊的历史原因造成的。

linker name:仅在编译链接时使用,gcc的-L选项应该指定linker name所在的目录。有的linker name是库文件的一个符号链接,有的linker name则是一段链接脚本。

重新编译我们的共享库

这里我们可以按照上述的命名规则,重新编译我们的共享库。

gcc -c -fPIC stack.c
ln -s libstack.so.1.0 libstack.so #编译时使用的linker name
gcc -shared -Wl,-soname,libstack.so.1 -o libstack.so.1.0 stack.o push.o pop.o is_empty.o
ln -s libstack.so.1.0 libstack.so.1 #执行时加载的soname
./main
  • real name:libstack.so.1.0 包含完整版本号
  • so name:libstack.so.1 只包含主版本号,编译共享库时指定
  • linker name:libstack.so 编译器只认linker name,即libxxx.so

链接静态库和共享库的区别

在链接共享库时只是指定了动态链接器和该程序所需要的库文件,并没有真的做链接,可执行文件main函数中调用的库函数仍然是未定义符号,要在运行时做动态链接。

而在链接静态库时,链接器会把静态库中的目标文件取出来,和可执行文件真正链接在一起。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap