C 语言静态库与动态库的生成和使用

在 YouTube 上找到一个视频 动态链接库静态链接库的生成和使用,它把用 GCC 生成静态库和动态库,以及如何使用他们说的很明白,有条件的可以直接看那个视频。本文就是一个观后的实操和笔记,加添了更多如何查看动态库,静态库,目标文件,执行文件的过程。

为什么要了解静态库和动态库呢?这有助于我们理解多模块的 C/C++ 代码是如何联合工作的。我们多数时候使用的 IDE, 一个 Build 帮我们做了太多的事情,反而使我们眼前一抹黑,这背后有怎么把一个个源文件编译成目标文件(*.o) 文件,或生成静态库/动态库,又如何连接静态库/动态库生成可执行文件,等等。

试验中使用的平台是 Linux, 如果没有 Linux 可通过 Docker 容器得到一个,如

$ docker run -it -v $(pwd):/work -w /work rust:1.78-buster bash

为什么使用 rust:1.78 镜像,其实也没什么特别的,因为当前在学习 Rust, 而正好该镜像中有 GCC 编译器。启动该容器后,为编辑需要,最好安装一个 vim,在容器中运行

apt update
apt install -y vim

若不用 Linux 也行,在 Windows, Mac OS X 也行,编译工具或有不同,我们可以在全平台下使用 GCC。并且清楚不同平台静态库和动态库文件的扩展名是不同的

  1. Windows: 静态库 *.lib, 动态库 *.dll
  2. Linux: 静态库 *.a, 动态库 *.so
  3. Mac OS X: 静态库 *.a, 动态库 *.dylib; *.framework 可能是静态库,也可能是动态库

以及在不同平台执行文件时如何定位动态库文件有所差异。

下面是在 Linux 下的演示,我们将要创建一个动态库 libadd.so 和静态库  libsub.a, 编译生成执行文件 main 时要连接这两个静态库和动态库,最后执行时只需依赖于动态库 libadd.so。

创建动态库

add.c

执行命令

$ gcc -fPIC -shared -o libadd.so add.c

生成了 libadd.so 动态库文件

创建静态库

sub.c

$ gcc -c sub.c    # 先生成  sub.o 目标文件
$ ar rcs libsub.a sub.o

生成了 libsub.a 静态文件

对于生成的目标文件 *.o, 静态库 *.a 和动态库 *.so,我们都可以用 objdump, readelf, 和 nm 来查看某些类型,如

objdump -S libsub.a                               # 反编译出汇编代码
objdump -T libadd.so                            # 列出导出的函数
readelf -s --dyn-syms libadd.so           # 查看符号信息
nm -D libadd.so                                      # 查看导出的函数

Windows 下用 dumpbin /EXPORTS add.dll 查看动态库导出的函数,Mac OS X 下尝试用 Linux 下相同的命令。objdum, readelf, 和 nm 具体的用法请用 --help 参数查看它们的帮助。 

使用静态库和动态库

main.c

main 函数前的 add, sub 是符号声明,可让 main.c 代码通过编译生成目标文件,在连接的过程中才去其他目标文件或静态库/动态库文件中找到相应的符号,完成连接

$ gcc -o main main.c -L. -lsub -ladd

生成了 main 可执行文件. 这一步也可以拆成两步来操作

$ gcc -c main.c               # 生成了 main.o 目标文件
$ gcc -o main main.o -L. -lsub -ladd     # 连接生成 main 执行文件

不用 -L. -l 的方式也可以直接直接要链接的库文件名,如

$ gcc -o main main.c libadd.so libsub.a       # 或
$ gcc -o main main.o libadd.so libsub.a

目标 main.o 中对 add, sub 函数的调用地址是 0,

$ objdump -t main.o
......
0000000000000000 *UND* 0000000000000000 add
0000000000000000 *UND* 0000000000000000 sub
......

这就是连接要做的事情,查看连接好生成的 main 文件

$ objdump -t main
......
0000000000000000 F *UND* 0000000000000000 add
......
000000000000119e g F .text 0000000000000012 sub
......

我们看到调用静态库的 sub 函数已经连接到了正确的函数地址,而调用动态库的 add 函数仍然是 0, 这就是所谓的动态,需要在执行时连接到正确的函数地址。

现在有了可执行文件 main, 试图执行

$ ./main
./main: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory

即例 libadd.so 与 main 处在相同的目录,但 main 不知道从它所在目录加载 libadd.so 文件.

Linux 下 /etc/ld.so.conf.d/ 目录中的配置文件配置了从哪些目录搜寻加载动态库文件

$ ls /etc/ld.so.conf.d/
libc.conf x86_64-linux-gnu.conf

比如它的内容是

$ cat /etc/ld.so.conf.d/libc.conf
# libc default configuration
/usr/local/lib

按照 /etc/ld.so.conf.d/libc.conf 文件中路径,可以把 libadd.so 丢到 /usr/local/lib 目录中,或是在 /etc/ld.so.conf.d 目录中创建新的文件,在其中加上当前 libadd.so 所在的目录路径。

或者用 LD_LIBRARY_PATH 环境变量

$ export LD_LIBRARY_PATH=/work
$ ./main
5+2=5, 5-2=2

动态连接成功,程序执行正常。

查看所链接的库

ldd main
linux-vdso.so.1 (0x00007ffe14069000)
libadd.so => /work/libadd.so (0x00007f673f52d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f673f364000)
/lib64/ld-linux-x86-64.so.2 (0x00007f673f539000)

如果是找不到 libadd.so 的情况下,用 ldd main 看到的是

ldd main
linux-vdso.so.1 (0x00007fffc9149000)
libadd.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc40e465000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc40e635000)

当前目录下所有文件

$ ls
add.c libadd.so libsub.a main main.c main.o sub.c sub.o

如果连接生成 main 执行文件时不指定 -ladd, 就会找不到 add 函数

$ gcc -o main main.o -L. -lsub
/usr/bin/ld: main.o: in function main':
main.c:(.text+0x13): undefined reference to
add'
collect2: error: ld returned 1 exit status

没有 -L. 也不行,没有 -lsub  也是不行的。-L. 告诉从当前目录中找动态库或静态库文件,-lsub, -ladd, 表示要从 -L.  指示的目录中查找 libsub.a, libsub.so, libadd.a, 或 libadd.so 文件。

本文链接 https://yanbin.blog/c-static-dynamic-library/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments