目录:
第十章 使用库
第一部分概述
1.库
2.库兼容
3.命名约定
4.常用库
第二部分库操作工具
5.库操作工具
6.nm命令
7.ar命令
8.ldd命令
9.ldconfig
第三部分静态库
10. 编写并使用静态库(.a)
11.静态库实例
第四部分共享库
12. 编写并使用共享库
13.soname
14.共享库的命名惯例
第五部分动态加载的方法 使用共享库
15. 动态加载
16. dl
注:原书639页,内容繁多,不易快速定位要点。在第一次阅读过程中,我摘抄要点、记录心得,形成该笔记,供日后查阅和再学习用。
说明:$表示终端输入命令
第一部分概述
1.库
可以被多个软件项目使用(和重用)的代码集
代码重用和实用工具
2.库兼容
库的多个修订或升级版本间保持稳定一致的变量、数据结构、公共函数接口和总体功能。
3.命名约定
库名:以lib开头
静态库:lib*.a(代表存档,archive)
共享库:lib*.so(代表共享目标文件,sharedobject)
编号: library_name.major_num.minor_num.patch_num
如libgdbm.so.2.1.0
当库的变化达到了和以前的版本不能兼容的程度时,增加主版本号(major_num)
当库有了新变化而又能和以前的版本保持兼容时,改变次版本号(minor_num)
为修正库中的错误而进行改动则会改变补丁级别号(patch_num),
补丁号有时也称发行号(releasenumber)
_g和_p结尾的库:如libform_g.a和libform_p.a
它们都是基本库libform.a的特殊版本,
_g:调试版本,它们编入了特殊的符号和功能,能够增加对采用了这个库的应用程序进行调试的能力。
_p:代码剖析,profiling.包含的代码和符号能够进行复杂的代码剖析和性能分析
4.常用库
库 头文件 描述 libGL.so <GL/gl.h> 实现到OpenGL API 的接口 libGLU.so <GL/glu.h> 实现到OpenGL API 的接口 libImlib.so <Imlib.h> 实现一套图像处理例程 libjpeg.so <jpeglib.h> 定义到JPEG库的接口,使用JPEG图像文件 libpng.so <png.h> 用于编码、 和操作PNG格式图像文件的参考实现 libc.so 实现标准C库(不需要头文件) libcrypt.so <crypt.h> 加密函数的集合 libz.so <zlib.h> 通用压缩例程库
第二部分库操作工具
5.库操作工具
(1). 创建、维护和管理编程库的功能
(2). nm\ar\ldd\ldconfig 等
6.nm命令
nm 列出编入目标文件或二进制文件的所有符号。
用途一:查看程序调用什么函数
用途二:查看一个给定的库或者目标文件是否提供所需要的函数
nm [options] file options: -C | --demangle 将符号名转化为用户级的名字。在让C++函数名可读方面特别有用 -u | --undefined-only 只显示未定义的符号 -l | --line-numbers 使用调试信息输出定义每个符号的行号,或者未定义符号的重定位项
7.ar命令
用来操作高度结构化的存档(archive)文件
该命令最常用来创建静态库--包含一个或多个目标文件,预编译格式的例程的目标文件称为成员(member)
ar [dmpqrtx] [member] archive files ... -c 如果存档文件不存在,则从多个文件创建存档文件,并且不显示ar发出的警告 -s 创建或升级从符号到定义它们的成员之间的交叉索引映射表 -r 向存档文件插入files,替换已有的任何同名成员。新成员添加到存档文件的末尾 -q 把files添加到存档文件而不检查是否进行替换
8.ldd命令
列出使程序正常运行所需要的共享库
ldd [options] file ldd输出file所要求的共享库的名字 -d 执行重定位并报告所有丢失的函数 -r 执行对函数和数据对象的重定位并报告丢失的任何函数或数据对象
9.ldconfig
和动态链接/装载工具ls.so系统工作,一起来创建和维护对最新版本共享库的链接
ldconfig [options] [libs] -p 仅打印出文件/et/ld.so.cache的内容,此文件是ld.so 所知道的共享库的当前列表 -v 更新/etc/lb.so.cache 的内容,列出每个库的版本号,扫描的目录和所有创建和更新的链接
第三部分静态库
10.编写并使用静态库(.a)
静态库(本质还是共享库)是包含了目标文件的文件,这些目标文件被称为模块或成员,
是可以重用的预编译代码。它们以特殊的格式和一个表或者映射保存在一起。
这个表或映射将符号名和保存该符号的成员名链接起来。映射加速了编译和链接的过程。
为了使用库代码,必须在源代码文件中包含适当的头文件并且链接到库。
11.静态库实例
(1).编写*.h文件,如mylib.h
/* * libmy.h * author: Jarvis Chu * date: 2011-9-6 */ #ifndef _LIBMY_H #define _LIBMY_H void sayHello(); int add(int param1,int param2); #endif
(2).编写*.c文件,如mylib.c
/* * libmy.c * author: Jarvis Chu * date: 2011-9-6 */ #include <stdio.h> #include "libmy.h" void sayHello() { printf("Hello Archive,Yes Static Libaray!\n"); } int add(int param1,int param2) { return param1+param2; }
下面建立静态库
(1).把代码编译为目标文件形式
$ gcc -c libmy.c -o libmy.o
得到mylib.o文件
(2).使用工具ar创建一个存档文件
$ ar rcs libmy.a libmy.o
得到libmy.a静态库文件
下面测试该库的使用
(1).编写代码test.c
/* * test.c * 用来测试 libmy.a 静态库的使用 * author: Jarvis Chu * date: 2011-9-6 */ #include <stdio.h> #include "libmy.h" int main(int argc, int **argv) { int a = 0; sayHello(); a = add(1,2); printf("a = %d\n",a); return 0; }
(2).编译test.c
$ gcc test.c -o test -static -L. -lmy -static 选项 将 test.o 文件和libmy 链接起来 -L. 表示搜索路径为当前目录(.) -lmy 告诉gcc查找库文件libmy
得到test可执行文件
(*3*).证实的确链接了静态库
$ file test
第四部分共享库
12.编写并使用共享库
(1). 共享库相对于静态库的优点:
<1> 共享库占用系统资源更少
由于共享库并没有被编译进每个二进制文件中,只是在运行时从单个文件--共享库链接加载
所以占用更少的磁盘空间,同时占用更好的内存空间。
<2> 共享库最低限度也比静态库快许多
因为它们只需要向内存加载一次。
<3> 共享库使得代码维护的工作大大简化
静态库需要重新编译链接新库
(2). 共享库工作原理
运行时,动态链接器/加载器ld.so把二进制文件中的符号链接到设当的共享库上。
(3). 创建共享库的方法
<1> 编译目标文件时使用GCC的-fPIC选项,这能产生与位置无关的代码并能加载到任何地址
<2> 使用GCC的-shared和-soname选项,-soname 指定共享库名称
<3> 使用GCC的-wl选项,把参数传递给链接器ld
<4> 使用GCC的-l选项显示的链接C库,以保证可以得到所需的启动(startup)代码
避免程序在使用不同的,可能是不兼容版本的C库的系统上不能启动执行
(4).共享库实例
<1>编写libmy.clibmy.h 文件(文件同上)
<2>编译获得目标文件 libmy.o
$ gcc -fPIC -g -c libmy.c -o libmy.o
<3>链接库获得g共享库文件libmy.so.1.0.0
$ gcc -g -shared -Wl,-soname,libmy.so -o libmy.so.1.0.0 lib.o -lc
若不把该库安装到/usr/lib或/usr上,则建立两个符号链接
<1> 用于soname的(即soname)
$ ln -s libmy.so.1.0.0 libmy.so.1
<2>链接程序在使用-lmy链接到libmy时使用的(即linkername)
$ ln -s libmy.so.1.0.0 libmy.so
(个人认为,这个libmy.so就相当于windows下动态链接库编程中的.lib文件, 而libmy.so.1.0.0相当于.dll文件)
使用该共享库
<1>编写test.c文件 (文件同上)
<2>编译链接生成 可执行文件test
$ gcc -g test.c -o test -L. -lmy 表示编译时链接当前目录的 libmy 库
<3>为了执行test程序,还要告诉ld.so在哪里找到这个共享库
方法一:$ LD_LIBRARY_PATH=$(pwd) ./test LD_LIBRARY_PATH 把它包含的路径添加到了库目录 方法二:把库路径添加到/etc/ld.so.conf,然后以root身份运行ldconfig 更新高速缓冲区/etc/ls.so.cache. 方法三:把库放进/usr/lib,建立一个到soname的符号链接,然后以root身份运行ldconfig
(此处类似Windows下的动态链接库的使用过程:编译时使用.lib运行时加载.dll)
13.soname
共享库的一个非常重要的,也是非常难的概念是:soname
——简写共享目标名(shortfor shared object name)。
这是一个为共享库(.so)文件而内嵌在控制数据中的名字。
如前面提到的,每一个程序都有一个需要使用的库的清单。
这个清单的内容是一系列库的soname,如同ldd显示的那样,共享库装载器必须找到这个清单。
soname的关键功能是它提供了兼容性的标准。
当要升级系统中的一个库时,并且新库的soname和老的库的soname一样,
用旧库连接生成的程序,使用新的库依然能正常运行。
这个特性使得在Linux下,升级使用共享库的程序和定位错误变得十分容易。
在Linux中,应用程序通过使用soname,来指定所希望库的版本。
库作者也可以通过保留或者改变soname来声明,哪些版本是相互兼容的,
这使得程序员摆脱了共享库版本冲突问题的困扰。
14.共享库的命名惯例
按照共享库的命名惯例,每个共享库有三个文件名:realname、soname和linkername。
真正的库文件(而不是符号链接)的名字是realname,包含完整的共享库版本号。
例如上面的libmy.so.1.0.0。
soname是一个符号链接的名字,只包含共享库的主版本号,
主版本号一致即可保证库函数的接口一致,因此应用程序的.dynamic段只记录共享库的soname,
只要soname一致,这个共享库就可以用。
例如libmy.so.1和libmy.so.2是两个主版本号不同的libmy,
有些应用程序依赖于libmy.so.1,有些应用程序依赖于libmy.so.2,
但对于依赖libmy.so.1的应用程序来说,
真正的库文件不管是libmy.so.1.1.0还是libcap.so.1.2.0都可以用,
所以使用共享库可以很方便地升级库文件而不需要重新编译应用程序,这是静态库所没有的优点。
注意libc的版本编号有一点特殊,libc-2.8.90.so的主版本号是6而不是2或2.8。
linkername仅在编译链接时使用,gcc的-L选项应该指定linkername所在的目录。
有的linkername是库文件的一个符号链接,有的linkername是一段链接脚本。
第五部分动态加载的方法 使用共享库
15.动态加载
和上述的使用方法不同,不是在编译时链接和运行时加载,
而是直接在运行时作为完全独立的模块使用dl(dynamicloading,动态加载)接口显示地加载。
(这点和Windows下的运行时动态加载LoadLibray也是一样的道理
参考:http://blog.csdn.net/jarvischu/article/details/6559059 和 http://blog.csdn.net/jarvischu/article/details/6562566)
16.dl
由libdl实现。
包含用来加载、搜索和卸载共享对象的函数。
要使用这些函数在源代码中包含<dlfcn.h>,然后在编译命令或makefile中使用-ldl和libdl链接
(1). 加载共享对象dlopen (类似LoadLibrary函数)
void *dlopen(const char *filename, int flag); 以flag指定的模式加载有filename指定的库文件 filename: 可以是 NULL,表示打开当前执行的文件,也就是你的应用程序。 flag: RTLD_LAZY, 表示来自加载的对象的符号在被调用时解析 RTLD_NOW, 表示来自被加载的对象的符号在函数dlopen返回前被解析 返回值: 找到filename则返回一个句柄,否则返回 NULL
(2). 使用共享对象 (类似 GetProcAddress函数)
void *dlsym(void *handle, char *symbol); 在handle指定的库文件中搜索在symbol中命名的符号或函数。 返回值:返回执行符号的空指针,若发生错误则返回 NULL
(3). 检查错误
const char *dlerror(void); 如果任何函数出错,则函数dlerror返回一个描述错误的字符串,再把错误字符串置 NULL
(4). 卸载共享对象 (类似 FreeLibrary函数)
int dlclose(void *handle);