3.2 共享库和静态库
即使上面的示例程序被成功编译和链接,在加载和运行可执行文件之前也需要最后一个步骤。
如果试图直接开始运行可执行文件,在大多数操作系统中会出现下面错误:
$ ./a.out
./a.out: error while loading shared libraries:
libgdbm.so.3: cannot open shared object file:
No such file or directory
这是因为GDBM包提供了一个共享库。这种类型的库需要特殊对待--他必须在可执行文件运行之前从硬盘加载到内存。
外部库通常提供两种格式:静态库和共享库。静态库是先前早已看到的'.a'文件。当程序链接静态库时,程序中使用库中的目标文件中的任何外部函数的机器码会被最终复制到可执行文件中。
共享库使用更高级的方法链接使可执行文件体积更小。他们使用'.so'扩展名代表共享对象。
一个可执行文件链接共享库时只需要包含一个很小的请求函数列表,而不是外部函数的所有机器码。在可执行文件开始运行之前,操作系统使用动态链接器将共享库中的外部函数的机器码从硬盘复制到内存。
动态链接使可执行文件更小并且节约硬盘空间,因为一份拷贝可以在很多程序中共享使用。大多数操作系统也提供虚拟内存机制,它允许共享库的一份拷贝在物理内存中被所有正在运行的程序使用,和硬盘一样节约内存空间。
共享库甚至可以使更新一个库文件而不需要重新编译使用它的程序成为可能(这个库提供的接口没有改变)。
因为这些优势gcc在大多数操作系统中如果他们支持编译程序默认使用共享库。当使用'-lNAME'选项链接静态库'libNAME.a'时编译器首先查找相关的名字相同并且后缀名为'.so'的共享库。
在这个例子中当编译器在链接路径下查找'libgdbm'库,它会在'/opt/gdbm-1.8.3/lib'目录下找到下面两个文件:
$ cd /opt/gdbm-1.8.3/lib
$ ls libgdbm.*
libgdbm.a libgdbm.so
必然的,共享库'libgdbm.so'优先于静态库'libgdbm.a'。
然而当可执行文件开始它的加载函数时必须查找共享库从而加载到内存中。默认情况下加载器只会在系统预先设置的目录下查找共享库,例如'/usr/local/lib'和'/usr/lib'。如果库文件不在这些目录下则必须将它添加到加载路径。
最简单的设置加载路径的方法是通过环境变量LD_LIBRARY_PATH设置。例如下面的命令设置'/opt/gdbm-1.8.3/lib'为加载路径,因此'libgdbm.so'可以被找到:
$ LD_LIBRARY_PATH=/opt/gdbm-1.8.3/lib
$ export LD_LIBRARY_PATH
$ ./a.out
Storing key-value pair... done.
现在可执行文件可以成功运行,打印信息和创建一个名为'test'的DBM文件,文件包含键值对'testkey'和'testvalue'。
LD_LIBRARY_PATH环境变量可以在适当的登陆文件中一次性设置,例如GNU Bash shell的配置文件'.bash_profile'。
多个共享库目录可以同时放在加载路径中,使用冒号分开列表DIR1:DIR2:DIR3:...:DIRN。例如,下面的命令将'/opt/gdbm-1.8.3'和'/opt/gtk-1.4'路径下的'lib'目录设置为加载路径:
$ LD_LIBRARY_PATH=/opt/gdbm-1.8.3/lib:/opt/gtk-1.4/lib
$ export LD_LIBRARY_PATH
如果加载路径中已经存在项目,可以使用扩展语法LD_LIBRARY_PATH=NEWDIRS:$LD_LIBRARY_PATH。例如下面的命令添加'/opt/gsl-1.5/lib'目录到加载路径:
$ LD_LIBRARY_PATH=/opt/gsl-1.5/lib:$LD_LIBRARY_PATH
$ echo $LD_LIBRARY_PATH
/opt/gsl-1.5/lib:/opt/gdbm-1.8.3/lib:/opt/gtk-1.4/lib
系统超级用户可以为所有用户设置LD_LIBRARY_PATH变量,把它添加到默认登陆脚本中,例如'/etc/profile'。在GNU系统中,一个系统范围内的路径也可以在加载器配置文件'/etc/ld.so.conf'中定义。
静态链接可以使用'-static'选项强制gcc不链接共享库:
$ gcc -Wall -static -I/opt/gdbm-1.8.3/include/
-L/opt/gdbm-1.8.3/lib/ dbmain.c -lgdbm
这会创建一个链接了静态库'libgdbm.a'的可执行文件,它不需要设置LD_LIBRARY_PATH环境变量或者将共享库放在默认目录下就可以运行:
$ ./a.out
Storing key-value pair... done.
正如前面提示的,也可以直接链接个人库文件只需要在命令行中指定库文件的全路径。例如,下面的命令会直接链接静态库'libgdbm.a',
$ gcc -Wall -I/opt/gdbm-1.8.3/include
dbmain.c /opt/gdbm-1.8.3/lib/libgdbm.a
下面的命令会链接共享库文件'libgdbm.so':
$ gcc -Wall -I/opt/gdbm-1.8.3/include
dbmain.c /opt/gdbm-1.8.3/lib/libgdbm.so
在后面一个例子中当运行可执行文件时任然需要设置库文件的加载路径。