一 使用库必要性
提高代码重用性。
二 库的种类:
1 静态库
一般以 *.a 命名。
程序编译时被加载,此后,只要程序不被重新编译,静态库就没有作用了(可以删掉)。
由 于静态库的代码在编译过程中已经被载入可执行程序,因此体积较大,如果有多个应用程序都用了同一个静态库,在存放可执行程序的硬盘中就会有这个静态库的多 份拷贝。如果他们同时在运行,那么在内存中也会有这个静态库的多份拷贝。但是如下面提到的动态库相比较,程序执行时间比较短,因为没有执行时库函数的加 载。所谓“以空间换时间”。
下面我们用一个实例说明静态库的编程和使用。
// 库函数: hellowlib.c
#include <stdlib.h>
void printhellow()
{
printf("hellow,now in lib routine/n");
return ;
}
首先生成目标文件: gcc -c printhellow.c –o printhellow.o
然后使用 ar ( archive )命令把目标文件制作库文件: ar cqs libhello.a printhellow.o
注意库文件名一定是 lib***.a 格式,不要忘了加 lib 作为前缀。
下面我们写一个程序调用静态库 libhello.a 中的 printhellow 函数。
//testlib.c
int main(int arc, char **argv)
{
printhellow();
return ;
}
下面编译: gcc -o testlib testlib.c -L ./ -lhello
即可生成可执行文件 testlib 。
注意上面的 -L (大写)指示库的路径在当前目录下。如果没有这个选项,就需要把库 libhello.a 加入到标准路径中。如 /usr/lib 中。 -l (小写)只需要跟 hello ,其他字符全部不要,否则出错。
2 动态库(共享库)
一般以 .so 命名( share object )
与静态库不同,共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。与上面提到的静态库相比,很是节约空间。但运行时需要载入,因此运行时间相对静态库而言比较长。所谓“以时间换空间”。
动态链接的意思就是在程序装载内存的时候才真正的把库函数代码链接进行确定它们的地址,并且就算有几个程序同时运行,内存也只存在一份函数代码。
动态库的代码要实现这样的功能,必须满足这样一种条件:能够被加载到不同进程的不同地址,所以代码要经过特别的编译处理,我们把这种经过特别处理的代码叫做 “ 位置无关代码( Position independed Code .PIC ) ” 。
位 置无关代码可以这样看,内存中的动态代码只有一份副本,但动态库的数据却可能有多份。由于每一个链接到动态的进程都可能会修改库的数据,所以每当有这种情 况,操作系统就复制出一份数据副本,然后修改进程的地址空间映射,使它指向新的数据副本,这样,进程最后修改的只是属于自己的那份数据。
更详细来说,某个程序的在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了。如果有,则让其共享那一个拷贝;只有没有才链接载入。这样的模式虽然会带来一些 “ 动态链接 ” 额外的开销,却大大的节省了系统的内存资源。我们所知 C 的标准库就是动态链接库,即系统中所有运行的程序共享着同一个 C 标准库的代码段。
正 如刚才所说,由于动态链接库函数不会被拷贝到可执行文件中。编译的时编译器只会做一些函数名之类的检查。程序运行时,被调用的动态链接库函数被安置在内存 的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须是相对地址,而不是绝对地址。在编译的时候,我们需要告诉编译器,这些对象文件是用来 做动态链接库的,所以要用地址无关代码( Position Independent Code ( PIC ))。
共享库又可分为动态链接( Dynamic linking )和动态加载( Dynamic loading )两种。
2.1 动态链接( Dynamic linking )
在编译程序时指定要连接的库,然后再程序运行时一开始就将库载入。这也称为隐式调用库函数。
示例仍然使用钢材程序,编译库:
gcc -fpic -shared -o libhello.so printhellow.c
-fpic 便指示了这是 地址无关代码, -shared 指示是一个共享库
编译程序,和静态库使用方法一样: gcc -o testlib testlib.c -L ./ -lhello
这时候编译成功,但我们执行 testlib 却发生以下错误:
./testlib: error while loading shared libraries: libhello.so: cannot load shared object file: No such file or directory
显然,程序加载时到标准路径中找不到库文件。
解决办法 1 )把我们生成的库放到标准路径中去即可。
mv libhello.so /usr/lib
2 )建立符号连接 ln -s `pwd`/libhello.so /usr/lib/libhello.so
3 )将动态链接库所在目录名追加到动态链接库配置文件 /etc/ld.so.conf 中
pwd >> /etc/ld.so.conf
ldconfig
4 )利用动态链接库管理命令 ldconfig, 强制其搜索指定目录 , 并更新缓存文件 ,# ldconfig `pwd`
但是这种方法只是暂时的,如果再次运行 ldconfig, 缓存文件内容可能改变 , 所需的动态链接库可能不被系统共享了。
2.2 动态加载( Dynamic loading )
程序不会自动加载库,需要在程序中由程序员指定何时加载。系统提供了一套动态加载 API 以方便我们使用。
先看一下这些 API :
1 )
#include <dlfcn.h>
void *dlopen( const char *file, int mode );
dlopen 第一个参数是共享库的名称,会在以下路径中查找指定的共享库:
①环境变量 LD_LIBRARY_PATH中指定的
② 文件 /etc/ld.so.cache 中找到的库的列表,由 ldconfig 命令刷新。
③ 目录 usr/lib 。
④ 目录 /lib 。
⑤ 当前目录。
第二个参数为打开共享库的方式。有两个值
①RTLD_NOW :将共享库中的所有函数加载到内存
②RTLD_LAZY :稍后进行共享库中的函数的加载,调用 dlsym() 时加载某函数
2 )
char *dlerror();
用 dlerror() 函数测试是否打开成功,并进行错误处理 ;
3 )
void *dlsym( void *restrict handle, const char *restrict name );
用 dlsym 获得函数地址 , 存放在一个函数指针中,用获得的函数指针进行函数调用。
4 )
char *dlclose( void *handle );
程序结束时用 dlclose 关闭打开的动态库,防止资源泄露。
下面是 testdyn.c 程序实例:
#include <dlfcn.h>
#include <stdlib.h>
#include <errno.h>
int main(int arc, char **argv)
{
void (*pfunc)(); /* pointer of function */
char *perr;
/* load shared library dynamicly */
void *handle = dlopen("libhello.so", RTLD_LAZY);
if(NULL == handle)
{
printf("dlopen error,errno = %d/n",errno);
return -1;
}
perr = dlerror();
if(NULL != perr)
{
printf("dlerror first /n");
return -1;
}
/* get the address of printhellow function */
pfunc = dlsym(handle, "printhellow");
perr = dlerror();
if(NULL != perr)
{
printf("dlerror when dlsym /n");
return -1;
}
/* call the function */
(*pfunc)();
dlclose(handle);
printf("load share library success!/n") ;
return -1;
}
编译: gcc -o testdyn testdyn.c –ldl
注意加上 –ldl 参数。
程序运行:
[root@VMwareLin7 fengxz]# ./testdyn
hellow,now in lib routine
load share library success!