对于程序员来说库(libraries)是必不可少的一个工具。可以将预先存在的代码进行编译,然后直接使用。它们经常提供通用的功能,像链表或者二叉树等可以用来存放各种数据,或者特定的功能像数据库的接口,比如MySQL。
大多数大的软件工程包含许多的模块,其中的一些模块可能会在后面别的项目中用到,或者你只是出于组织的意愿想将其独立开。当你有一个重用的,或者逻辑独特的函数集,你将其编为一个库将会是非常有帮助的,这样你就不需要将其拷贝到当前的项目并且老是重新编译它--这样就可以保证你的程序的不同模块不会被互相影响,当你改变其中一个的时候不会影响其它的模块。一旦你你写完并且经过测试后,你就可以安全地重复地使用它。并且在每次将它build到你的工程中节省时间和精力。
Build静态库很简单,并且我们很少遇到问题,所以此文将不包含这方面。我还是谈谈动态库,大多数人对此比较困惑。
在我们开始之前,快速的介绍一些关于从源代码到可执行程序的过程中究竟发生了什么会比较有帮助:
#ifndef foo_h__
#define foo_h__
extern void foo(void);
#endif // foo_h__
foo.c
#include
void foo(void)
{
puts("Hello, I'm a shared library");
}
main.c
#include
#include "foo.h"
int main(void)
{
puts("This is a shared library test...");
foo();
return 0;
}
foo.h文件定义了到我们库中的接口,一个简单的函数foo()。foo.c包含着该函数的实现,main.c是一个驱动程序来使用我们的库。
$ gcc -c -Wall -Werror -fpic foo.c
步骤2:从对象文件创建一个共享库
$ gcc -shared -o libfoo.so foo.o
步骤3:用共享库链接
$ gcc -Wall -o test main.c -lfoo
/usr/bin/ld: cannot find -lfoo
collect2: ld returned 1 exit status
告诉GCC去哪里寻找共享库
$ gcc -L/home/username/foo -Wall -o test main.c -lfoo
步骤4:让库在运行时可找到
$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
加载器找不到共享库。我们没有在标准路径中安装它,所以我们需要给加载器一点指示。我们有好几种方法:我们可以使用环境变量LD_LIBRARY_PATH,或者rpath。我们首先来看一下LD_LIBRARY_PATH:
$ echo $LD_LIBRARY_PATH
没有任何东西。让我们通过预置我们的工作目录到LD_LIBRARY_PATH中修复它:
$ LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
发生什么了?我们的目录已经在LD_LIBRARY_PATH中了,但是我们并没有export它。在Linux中,如果你不export改变到环境变量中,它就不会被子进程所继承。加载器和我们的测试程序没有继承我们所做的改变。 幸运的是,修复它很简单:
$ export LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
This is a shared library test...
Hello, I'm a shared library
很好,它工作了!LD_LIBRARY_PATH对于你没有所在系统的admin权限时快速测试是很好的。当然缺点是,export LD_LIBRARY_PATH变量意味着对你正在运行的其它程序来说,这些程序也依赖于LD_LIBRARY_PATH,但你做完后,如果你不reset它到之前的状态,
它可能导致问题。
$ unset LD_LIBRARY_PATH
$ gcc -L/home/username/foo -Wl,-rpath=/home/username/foo -Wall -o test main.c -lfoo
$ ./test
This is a shared library test...
Hello, I'm a shared library
Excellent,它工作了。rpath方法是很好的,因为每一个程序独立地得到共享库的位置,所以不会出现不同的程序查找错误的路径,像我们之前提到的LD_LIBRARY_PATH。
$ cp /home/username/foo/libfoo.so /usr/lib
$ chmod 0755 /usr/lib/libfoo.so
现在文件放在了标准位置,有了正确的权限,对每个用户都是可读的。我们需要告诉加载器可以使用了,所以我们更新cache:
ldconfig
这将创建一个到我们的共享库的链接,并且更新cache.so,使得它可以立即被使用。让我们再次检查一下:
$ ldconfig -p | grep foo
libfoo.so (libc6) => /usr/lib/libfoo.so
现在我们的库安装成功。在测试它之前,我们必须清理一些东西:
$ unset LD_LIBRARY_PATH
重新link我们的可执行文件。注意这次我们不需要-L选项,因为我们的库是安装在默认位置的,并且我们也不再使用rpath选项:
gcc -Wall -o test main.c -lfoo
让我们确保我们使用的是我们库的/usr/lib实例:
$ ldd test | grep foo
libfoo.so => /usr/lib/libfoo.so (0x00a42000)
很好,我们在跑一次:
$ ./test
This is a shared library test...
Hello, I'm a shared library
差不多都结束了。我们覆盖了如何build一个共享库,如何链接它,如何解决常见的和共享库有关的加载器问题--以及不同方法的优劣点。
此文翻译自“Shared libraries with GCC on Linux”