Linux中的共享库和gcc

对于程序员来说库(libraries)是必不可少的一个工具。可以将预先存在的代码进行编译,然后直接使用。它们经常提供通用的功能,像链表或者二叉树等可以用来存放各种数据,或者特定的功能像数据库的接口,比如MySQL。

大多数大的软件工程包含许多的模块,其中的一些模块可能会在后面别的项目中用到,或者你只是出于组织的意愿想将其独立开。当你有一个重用的,或者逻辑独特的函数集,你将其编为一个库将会是非常有帮助的,这样你就不需要将其拷贝到当前的项目并且老是重新编译它--这样就可以保证你的程序的不同模块不会被互相影响,当你改变其中一个的时候不会影响其它的模块。一旦你你写完并且经过测试后,你就可以安全地重复地使用它。并且在每次将它build到你的工程中节省时间和精力。

Build静态库很简单,并且我们很少遇到问题,所以此文将不包含这方面。我还是谈谈动态库,大多数人对此比较困惑。

在我们开始之前,快速的介绍一些关于从源代码到可执行程序的过程中究竟发生了什么会比较有帮助:

  1. C预处理器:这个阶段处理所有的预处理器指令。基本上,预处理指令包括任何以#开始的行,例如#define和#include
  2. 编译:一旦源代码文件被预处理后,然后就是编译。因为许多人将entire build process理解为编译,这个阶段也被称为"compliation proper"。这个阶段将.c文件变为.o(object)文件。
  3. 链接:这里是所有的对象文件和一些库文件链接到一起生成你最终的程序。注意:对于静态链接库,实际的库是放置到你最终的程序中的,但是对于动态链接库,只是将该库的一份引用(reference)放进去了。现在你就有了一份完整的可以运行的程序了。你从shell中launch它,程序传递到加载器。
  4. 加载:当你的程序启动后,该阶段发生。你的程序扫描到动态库的引用。任何被发现的引用将库匹配到你的程序中。
步骤3和步骤4是共享库施展魔术的地方。
现在,回到我们非常简单的例子上来。
foo.h
#ifndef foo_h__
#define foo_h__
 
extern void foo(void);
 
#endif  // foo_h__
foo.c
#include <stdio.h>
 
 
void foo(void)
{
    puts("Hello, I'm a shared library");
}
main.c
#include <stdio.h>
#include "foo.h"
 
int main(void)
{
    puts("This is a shared library test...");
    foo();
    return 0;
}
foo.h文件定义了到我们库中的接口,一个简单的函数foo()。foo.c包含着该函数的实现,main.c是一个驱动程序来使用我们的库。
出于该例子的目的,所欲的文件都放置在了/home/username/foo
步骤1:用Position Independent Code进行编译
我们需要编译我们的库的源文件到position-independent code(PIC):
$ gcc -c -Wall -Werror -fpic foo.c
步骤2:从对象文件创建一个共享库
现在我们需要将对象文件生成共享库。我们称它为libfoo.so:
$ gcc -shared -o libfoo.so foo.o
步骤3:用共享库链接
正如你所见,实际上非常简单。我们有一个共享库。我们编译main.c并且用libfoo进行链接。我们称我们最终的程序为“test”。注意-lfoo参数不是用来查找foo.o。GCC默认所有哦的库名以'lib'开始,并以'.so'或者'.a'结尾。
$ gcc -Wall -o test main.c -lfoo
/usr/bin/ld: cannot find -lfoo
collect2: ld returned 1 exit status
告诉GCC去哪里寻找共享库
链接器不知道去哪里寻找libfoo。GCC默认有一个位置列表去寻找它,但是我们的目录并不在这个列表中。我们需要告诉GCC去哪里寻找libfoo.so。我们使用-L参数来寻找。在这个例子中,我们将使用当前目录,/home/username/foo:
$ 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:
使用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它到之前的状态, 它可能导致问题。
使用rpath
现在我们尝试使用rpath(首先我们需要清除LD_LIBRARY_PATH来确保是rpath找到了我们的库)。Rpath,或者说是run path,是一种嵌入共享库的位置到可执行程序中的一种方法,相反它不依赖于默认的位置或者环境变量。我们做这些在链接阶段。注意很长的"-Wl, -rpath=/home/username/foo"选项。-WI发送逗号分隔的选项到链接器中,所以我们告诉它发送-rpath选项,使用我们的工作目录,到链接器中。
$ 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。
rpath vs. LD_LIBRARY_PATH
rpath也有一些缺点。首先,它需要共享库安装在一个固定的位置,使得你的程序的所有的用户将访问在这些位置的库。这意味着缺乏在系统配置上缺乏灵活性。第二,如果库指到一个NFS mount的路径下或者其它的network drive下,你可能需要经历过长的延迟或者程序启动上变得更糟。
使用ldconfig来修改ld.so
如果我们想要安装我们库使得所有的用户都可以使用它该怎么办呢?首先,你需要管理员权限。理由有二:第一,将库发到一个标准的位置,可能是/usr/lib或者/usr/local/lib,普通的用户是无法做到这一点的。第二,你需要修改ld.so配置文件和cache。作为root用户,执行如下命令:
$ 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
现在我们的库安装成功。在测试它之前,我们必须清理一些东西:
再一次清除LD_LIBRARY_PATH,以防万一:
$ 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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值