库文件的存在极大的提高了 C/C++ 程序的复用性,本文简单介绍如何生成、使用它们。
命名约定:
我们一般对库文件约定以 lib
开头,动态库文件一般以 .so
(*nix)或 .dll
(Windows) 结尾,静态库文件一般以 .a
(*nix)或 .lib
(Windows) 结尾。
动态库还分为两种用法:
- 在编译期间声明动态库的存在,运行时由系统链接器自动链接
- 在运行期间动态加载和卸载,由用户程序决定何时链接
下面我们以 Linux 为例,Windows 的用法与此有所不同,但思路大同小异。
生成和使用静态链接库
将库源码编译成目标文件:
$ gcc -c struct.c [-o struct.o]
打包库文件:
$ ar cr libstruct.a struct.o # 注意参数顺序不能错
这里的 ar
和 tar
类似,是一种打包工具。
可以使用 $ ar -t libstruct.a
命令 检查包含哪些 .o
文件。
构建符号表:
$ ranlib libstruct.a
这个命令不是必须的,是因为部分 ar
的实现集成了该功能,此时的 ranlib
就是一个空壳命令。
链接静态库,生成可执行文件:
$ gcc main.c -static -L . -lstruct -o main
因为我们约定库文件以 lib
开头,所以这里可以省掉它。
设置库文件的环境路径:
- 定义
LD_LIBRARY_PATH
环境变量,添加动态库文件的路径。 - 把库路径添加到
ld.so.conf
文件中,然后用ldconfig
加载。 - 使用
ldconfig
临时加载。
编译时,如果出现重名的动态库和静态库,优先使用动态库。
生成和使用动态链接库
编译动态链接库文件:
$ gcc -shared -fPIC struct.c -o libstruct.so
这其实融合了好几条命令,但我们不做细述。
-fPIC
表示编译为“地址无关”的代码,用于编译共享库。如果不加 -fPIC
则加载 so 文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 so 文件代码段的进程在内核里都会生成这个 so 文件代码段的副本。
一般总是用 -fPIC
来生成 .so
文件,也从来不用fPIC来生成 .a
文件。
一般以下几种情况不需要:
- 该库可能需要经常更新
- 该库需要非常高的效率,尤其是有很多全局量的使用时
- 该库并不大
- 该库基本不需要被多个应用程序共享
链接动态库,生成可执行文件:
$ gcc main.c -L . lstruct -o main
一般我们习惯将动态链接库放入系统库目录:
$ gcc -shared -Wl,-soname,libstruct.so.1 -o libstruct.so.1 *.o
其中 -Wl
前缀的命令会传递给 ld
命令。我们之前说过,gcc
融合了多条命令。
也就是在链接 .o
文件时会执行:
$ ld -soname libstruct.so.1
libstruct.so
用于在编译期间使用 -lstruct
找到动态库,而 libstruct.so.1
则在运行期间真正被链接。
$ mv libstruct.so.1 /opt/lib
$ ln -sf /opt/lib/libstruct.so.1 /opt/lib/libstruct.so.1
$ ln -sf /opt/lib/libstruct.so.1 /opt/lib/libstruct.so
查看程序对动态库的依赖
$ ldd main
libstruct.so.1 => /opt/lib/libstruct.so.1 (0x00002aaaaaaac000)
libc.so.6 => /lib64/tls/libc.so.6 (0x0000003aa4e00000)
/lib64/ld-linux-x86-64.so.2 (0x0000003aa4c00000)
ldd 脚本中文详解
查看 obj 文件的符号表
obj 文件的格式和组成可能是系统差异性的一大体现,比如 Windows 下的 PE、Linux 和部分 Unix 下的 elf、Mac OS 下的 mach-o、aix 下的 xcoff。
使用 nm
、objdump
、readelf
等命令可以查看 .o
.a
.so
文件中的符号信息。
比如,下面这将打印出 hello.o 中未定义的符号:
$ nm -u hello.o
在运行时动态加载和卸载库
需要应用程序希望设计成插件化的架构,这就需要可以动态加载和卸载库的机制。与动态链接不同的是,动态加载的意思是,编译期间可以对动态库的存在一无所知,而是在运行期间通过用户程序尝试加载进来的。
通过 dlfcn.h
中的 dlopen
、dlsym
、dlclose
等函数实现此种功能。
另外,使用 dlfcn
机制需要在链接时使用 -rdynamic
选项,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被 static 修饰的函数)都添加到动态符号表(即 .dynsym
表)里。
GNU Libtool
如今许多软件的编译都采用 libtool
工具,libtool 是一个编译链接包装工具,实际只是一个脚本,用 libtool 编译和链接会产生类似 .la
的文件,这种文件其实是个文本文件,指向 .a
文件,并声明一些版本信息。
更多
如果想更好地了解,需要多看 gcc / ar / ldd / nm 的参数。