C 语言静态库与动态库的生成和使用
在 YouTube 上找到一个视频 动态链接库静态链接库的生成和使用,它把用 GCC 生成静态库和动态库,以及如何使用他们说的很明白,有条件的可以直接看那个视频。本文就是一个观后的实操和笔记,加添了更多如何查看动态库,静态库,目标文件,执行文件的过程。
为什么要了解静态库和动态库呢?这有助于我们理解多模块的 C/C++ 代码是如何联合工作的。我们多数时候使用的 IDE, 一个 Build 帮我们做了太多的事情,反而使我们眼前一抹黑,这背后有怎么把一个个源文件编译成目标文件(*.o) 文件,或生成静态库/动态库,又如何连接静态库/动态库生成可执行文件,等等。
试验中使用的平台是 Linux, 如果没有 Linux 可通过 Docker 容器得到一个,如
以及在不同平台执行文件时如何定位动态库文件有所差异。
下面是在 Linux 下的演示,我们将要创建一个动态库 libadd.so 和静态库 libsub.a, 编译生成执行文件 main 时要连接这两个静态库和动态库,最后执行时只需依赖于动态库 libadd.so。
执行命令
对于生成的目标文件 *.o, 静态库 *.a 和动态库 *.so,我们都可以用
main 函数前的
现在有了可执行文件 main, 试图执行
Linux 下 /etc/ld.so.conf.d/ 目录中的配置文件配置了从哪些目录搜寻加载动态库文件
或者用
查看所链接的库
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
为什么要了解静态库和动态库呢?这有助于我们理解多模块的 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若不用 Linux 也行,在 Windows, Mac OS X 也行,编译工具或有不同,我们可以在全平台下使用 GCC。并且清楚不同平台静态库和动态库文件的扩展名是不同的
apt install -y vim
- Windows: 静态库 *.lib, 动态库 *.dll
- Linux: 静态库 *.a, 动态库 *.so
- Mac OS X: 静态库 *.a, 动态库 *.dylib; *.framework 可能是静态库,也可能是动态库
以及在不同平台执行文件时如何定位动态库文件有所差异。
下面是在 Linux 下的演示,我们将要创建一个动态库 libadd.so 和静态库 libsub.a, 编译生成执行文件 main 时要连接这两个静态库和动态库,最后执行时只需依赖于动态库 libadd.so。
创建动态库
add.c1int add(int a, int b) {
2 return a + b;
3}执行命令
$ gcc -fPIC -shared -o libadd.so add.c生成了 libadd.so 动态库文件
创建静态库
sub.c1int sub(int a, int b) {
2 return a - b;
3}$ gcc -c sub.c # 先生成 sub.o 目标文件生成了 libsub.a 静态文件
$ ar rcs libsub.a sub.o
对于生成的目标文件 *.o, 静态库 *.a 和动态库 *.so,我们都可以用
objdump, readelf, 和 nm 来查看某些类型,如objdump -S libsub.a # 反编译出汇编代码Windows 下用
objdump -T libadd.so # 列出导出的函数
readelf -s --dyn-syms libadd.so # 查看符号信息
nm -D libadd.so # 查看导出的函数
dumpbin /EXPORTS add.dll 查看动态库导出的函数,Mac OS X 下尝试用 Linux 下相同的命令。objdum, readelf, 和 nm 具体的用法请用 --help 参数查看它们的帮助。 使用静态库和动态库
main.c 1#include "stdio.h"
2
3int add(int a, int b);
4int sub(int a, int b);
5
6int main() {
7 int m = add(5, 2);
8 int n = sub(5, 2);
9
10 printf("%1$d+%2$d=%d, %1$d-%2$d=%d\n", 5, 2, m, n);
11 return 0;
12}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 # 或目标 main.o 中对 add, sub 函数的调用地址是 0,
$ gcc -o main main.o libadd.so libsub.a
$ objdump -t main.o这就是连接要做的事情,查看连接好生成的 main 文件
......
0000000000000000 *UND* 0000000000000000 add
0000000000000000 *UND* 0000000000000000 sub
......
$ objdump -t main我们看到调用静态库的 sub 函数已经连接到了正确的函数地址,而调用动态库的 add 函数仍然是 0, 这就是所谓的动态,需要在执行时连接到正确的函数地址。
......
0000000000000000 F *UND* 0000000000000000 add
......
000000000000119e g F .text 0000000000000012 sub
......
现在有了可执行文件 main, 试图执行
$ ./main即例 libadd.so 与 main 处在相同的目录,但 main 不知道从它所在目录加载 libadd.so 文件.
./main: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
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按照 /etc/ld.so.conf.d/libc.conf 文件中路径,可以把 libadd.so 丢到 /usr/local/lib 目录中,或是在 /etc/ld.so.conf.d 目录中创建新的文件,在其中加上当前 libadd.so 所在的目录路径。
# libc default configuration
/usr/local/lib
或者用
LD_LIBRARY_PATH 环境变量$ export LD_LIBRARY_PATH=/work动态连接成功,程序执行正常。
$ ./main
5+2=5, 5-2=2
查看所链接的库
ldd main如果是找不到 libadd.so 的情况下,用 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)
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如果连接生成 main 执行文件时不指定 -ladd, 就会找不到 add 函数
add.c libadd.so libsub.a main main.c main.o sub.c sub.o
$ 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's Blog[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。