在 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。并且清楚不同平台静态库和动态库文件的扩展名是不同的
- Windows: 静态库 *.lib, 动态库 *.dll
- Linux: 静态库 *.a, 动态库 *.so
- Mac OS X: 静态库 *.a, 动态库 *.dylib; *.framework 可能是静态库,也可能是动态库
以及在不同平台执行文件时如何定位动态库文件有所差异。
下面是在 Linux 下的演示,我们将要创建一个动态库 libadd.so 和静态库 libsub.a, 编译生成执行文件 main 时要连接这两个静态库和动态库,最后执行时只需依赖于动态库 libadd.so。
创建动态库
add.c
1 2 3 |
int add(int a, int b) { return a + b; } |
执行命令
$ gcc -fPIC -shared -o libadd.so add.c
生成了 libadd.so 动态库文件
创建静态库
sub.c
1 2 3 |
int sub(int a, int b) { return a - b; } |
$ 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
1 2 3 4 5 6 7 8 9 10 11 12 |
#include "stdio.h" int add(int a, int b); int sub(int a, int b); int main() { int m = add(5, 2); int n = sub(5, 2); printf("%1$d+%2$d=%d, %1$d-%2$d=%d\n", 5, 2, m, n); return 0; } |
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 functionmain':
add'
main.c:(.text+0x13): undefined reference to
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
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。