一个程序的前世今生(三)——动态链接库和静态链接库
简介:
在程序编写的时候我们会依赖很多底层的实现(除非单纯操作简单的硬件如单片机点灯),所以不可能永远从零开始书写,因此不可避免的会使用到很多库文件,那么什么是库文件。
一:什么是库
库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。
二:什么是静态库
在计算机科学中,静态库(英语:Static library, Statically-linked library),或称静态库,是一个外部函数与变量的集合体。静态库的文件内容,通常包含一堆程序员自定的变量与函数,其内容不像动态链接库那么复杂,在编译期间由编译器与链接器将它集成至应用程序内,并制作成目标文件以及可以独立运作的可执行文件。而这个可执行文件与编译可执行文件的程序,都是一种程序的静态创建(static build)。以过去的观点来说,库只能算是静态(static)类型。
使用静态方式编译hello文件,执行如下命令: gcc -static -o hello hello.c, 标准库将被静态的链接到可执行文件中,多个可执行文件使用同一个静态库将会使静态库拷贝多份,对比静态方式和动态方式编译出的可执行文件
2.1 生成静态库的方法如下:
- 编译object文件。例如:
cc -Wall -c test1.c test2.c
,该命令会生成test1.o
和、test2.o
(其中-Wall
表示编译时输出警告)。 - 创建库文件。
ar -cvq libctest.a test1.o test2.o
。该命令会得到一个libctest.a
文件 - 可以通过
ar -t
查看.a
文件中包含哪些.o
。所以,实际上ar
就是一个打包命令,类似tar
- 构建符号表。
ranlib libctest.a
用于为.a
创建符号表。有些ar命令实际上已经集成了ranlib
的功能
三: 什么时动态库
动态库即动态链接库。与静态库相反,动态库在链接时不复制(目标程序中只会存储指向动态库的引用),程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用,节省内存。
编译可执行文件使用动态库时不添加static参数,参考上图。
3.1 生成动态库的方法如下:
- 编译object文件时使用
-fPIC(位置无关码)
选项: -
gcc -Wall -fPIC -c *.c
这个选项的目的是让编译器生成地址无关(position independent)的代码,这是因为,动态库是在运行期间链接的,变量和函数的偏移量是事先不知道的,需要链接以后根据offset
进行地址重定向。
- 使用
-shared
链接gcc -shared -Wl,-soname,libtest.so.1 -o libtest.so.1.0 *.o
-shared
选项是让动态库得以在运行期间被动态链接;-Wl,options
是设置传递给ld(链接器)
的参数,在上面的例子中,当链接器在链接.o
时会执行ld -soname ibtest.so.1
- 创建软链
上面的命令将最终输出一个动态库libtest.so.1.0
,而出于习惯,会创建两个软链:
mv libtest.so.1.0 /opt/lib ln -sf /opt/lib/libtest.so.1.0 /opt/lib/libtest.so.1 ln -sf /opt/lib/libtest.so.1.0 /opt/lib/libtest.so
libtest.so
用于在编译期间使用-ltest
让编译器找到动态库,而libtest.so.1
用于在运行期间链接。
3.2 运行期间查找动态库
运行期间,系统需要知道到哪里去查找动态库,这是通过/etc/ld.so.conf
配置的。ldconfig
用于配置运行时动态库查找路径,实际是更新/etc/ld.so.cache。另外一些环境变量也可以影响查找:(Linux/Solaris: LD_LIBRARY_PATH
, SGI: LD_LIBRARYN32_PATH
, AIX: LIBPATH
, Mac OS X: DYLD_LIBRARY_PATH
, HP-UX: SHLIB_PATH
)
3.3 动态加载和卸载的库
需要应用程序希望设计成插件化的架构,这就需要可以动态加载和卸载库的机制。与动态链接不同的是,动态加载的意思是,编译期间可以对动态库的存在一无所知,而是在运行期间通过用户程序尝试加载进来的。
通过dlfcn.h
中的dlopen
、dlsym
和dlclose
等函数实现此种功能。
另外,使用到dlfcn
机制的可执行文件需要使用-rdynamic
选项,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被static修饰的函数)都添加到动态符号表(即.dynsym表)里。