Linux静态库与动态库简介

以下内容引述至《Linux/Unix系统编程手册》

静态库

静态库被称为归档文件,它是Unix系统提供的第一种库。静态库能带来以下好处:

  • 可以将一组经常被用到的目标文件组织进单个库文件,这样就可以使用它来构建多个可执行程序并且在构建各个应用程序的时候无需重新编译原来的源代码文件;
  • 链接命令变得更加简单。在链接命令行中只需要指定静态库的名称即可,而无需一个个地列出目标文件了。链接器知道如何搜索静态库并将可执行程序需要的对象抽取出来

从结果上看,静态库实际上就是一个保存所有被添加到其中的目标文件的副本的文件。根据惯例,静态库的名称的形式为libname.a

共享库

静态库因为冗余而存在几个缺点

  • 存储同一个目标模块的多个副本会浪费磁盘空间,并且所浪费的空间是比较大的;
  • 如果几个使用了同一模块的程序在同一时刻运行,那么每个程序会独立地在虚拟内存中保存一份目标模块的副本,从而提高系统中虚拟内存的整体使用量;
  • 如果需要修改一个静态库中的一个目标模块,那么所有使用那个模块的可执行文件都必须要重新进行链接以合并这个更改。

共享库的思想是目标模块的单个副本由所有需要这些模块的程序共享。目标模块不会被复制到连接过的可执行文件中。
虽然共享库的代码是由多个进程共享的,但其中的变量却不是的。每个使用库的进程会拥有自己的在库中定义的全局和静态变量的副本。
还有其他的优势

  • 由于整个程序的大小变得更小了,因此在一些情况下,程序可以完全被加载进内存中,从而能够更快地启动程序
  • 由于目标模块没有被复制进可执行文件中,而是在共享库中集中维护的,因此在修改目标模块时无需重新链接程序就能够看到变更,甚至在运行着的程序正在使用共享库的现有版本的时候也能够进行这样的变更

主要的开销

  • 在概念上以及创建共享库和构建使用共享库的程序的实践上,共享库比静态库更复杂
  • 共享库在编译时必须要使用位置独立的代码,这在大多数架构上都会带来性能开销
  • 在运行时必须要执行符号重定位

创建动态库

$ gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
$ gcc -g -shared -o libfoo.so mod1.o mod2.o mod3.0

第一次命令创建了三个目标模块;
第二次命令创建了一个包含这三个目标模块的共享库

根据惯例,共享库的前缀为lib,后缀为.so(表示shared object)

$ gcc -g -fPIC -Wall mod1.c mod2.c mod3.c -shared -o libfoo.so

位置独立的代码

cc -fPIC选项指定编译器应该生成位置独立的代码,这会改变编译器生成执行特定操作的代码的方式,包括访问全局、静态和外部变量,访问字符串常量,以及获取函数的地址。
检查是否使用了-fPIC选项

$ nm mod1.o | grep _GLOBAL_OFFSET_TABLE_
$ readelf -s mod1.o | grep _GLOBAL_OFFSET_TABLE_

相应地,如果使用下面的命令中的任意一个产生了任何输出,那么指定的共享库中至少存在一个目标模块在编译时没有指定-fPIC选项

$ objdump --all-headers libfoo.so | grep TEXTREL
$ readelf -d lubfoo.so | grep TEXTREL

使用一个共享库

LD_LIBRARY_PATH环境变量
如果定义了LD_LIBRARY_PATH,那么动态链接器会在查找标准库目录之前会先查找该环境变量列出的目录中的共享库。

使用共享库的有用工具

ldd

ldd(列出动态依赖)命令显示了一个程序运行所需的共享库。

$ ldd prog

输出的形式是

library-nam => resolves-to-path

objdump和readelf

该命令可以获得各类信息——包括反汇编的二进制机器码——从一个可执行文件、编译过的目标以及共享库中。还能够显示这些文件中各个ELF节的头部信息

nm命令

nm命令会列出目标库或可执行程序中定义的一组符号,这个命令的一种用途是找出哪些库定义了一个符号文件

$ nm -A /usr/lib/lib*.so 2 > /dev/null | grep ' crypt$'

运行时共享库

动态链接器会按照下面的规则来搜索共享库:

  • 如果可执行文件的DT_PATH运行时库路径列表(rpath)中包含目录并且不包含DT_RUNPATH,那么就搜索这些目录
  • 如果定义了LD_LIBRARY_PATH环境变量,那么就会轮流搜索该变量值中以冒号分隔的各个目录
  • 如果可执行文件DT_RUNPATH运行库路径列表中包含目录,那么就会搜索这些目录
  • 检查/etc/ld.so.cache文件已确认它是否包含了与库相关的条目
  • 搜索/lib和/usr/lub目录

加载动态库

  • dlopen()函数打开一个共享库,返回一个供后续调用使用的句柄;
  • dlsym()函数在库中搜索一个符号并返回其地址;
  • dlclose()函数关闭之前由dlopen()打开的库;
  • dlerror()函数返回一个错误消息字符串,在调用上述函数中的某个函数发生错误时可以使用这个函数获取错误消息
#include <dlfcn.h>
void *dlopen(const char* libfilename, int flags);

flags参数是一个位掩码,它的取值是RTLD_LAZY和RTLD_NOW中的一个
RTLD_LAZY: 只有当代码被执行的时候才解析库中未定义的函数符号。如果需要某个特定符号的代码没有被执行到,那么永远都不会解析该符号。延迟解析只适用于函数引用,对变量的引用会被立即解析
RTLD_NOW:在dlope()结束之前立即加载库中所有的未定义符号,不管是否需要用到这些符号
RTLD_GLOBAL:这个库及其依赖树中的符号在解析由这个进程加载的其他库中的引用和通过dlsym()查找时可用
RTLD_LOCAL:与RTLD_GLOBAL相反,如果不指定任何常量,那么就取这个默认值
RTLD_NODELETE:在调用了dlclose()中不卸载库,即使其引用计数变成了0,即使后面重新通过dlopen()加载库时不会重新初始化库中的静态变量
RTLD_NOLOAD:不加载库,用于检查库是否已经被加载到进程的地址空间中了
RTLD_DEEPBIND:先搜索库中的定义,在搜索已加载的库中的定义

#include <dlfcn.h>
void *dlsym(void* handle, char* symbol);

*(void**)(&funcp) = dlsym(handle, symbol);

使用*(void**)语句不会出现“”ANSI C forbids the use of cast expressiongs as lvalue"的警告,因为是在向赋值语句中的左值指向的地址赋值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值