来源:
在Linux下如何使用自己的库函数-riverok-ChinaUnix博客 http://blog.chinaunix.net/uid-21393885-id-88128.html
构建Linux下的函数库编译方案 - observer的日志 - 网易博客 http://blog.163.com/[email protected]/blog/static/23180765201111622915148/
linux下的函数库 程序 编译_ablab_新浪博客 http://blog.sina.com.cn/s/blog_557366df0100l5m1.html
1 为什么要使用库?
关于代码复用的途径,一般有两种。
粘贴复制
这是最没有技术含量的一种方案。如果代码小,则工作量还可以忍受,如果代码很庞大,则此法不可取。即便有人原意这样做,但谁又能保证所有的代码都可得到呢?
而库的出现很好的解决了这个问题。
使用函数库
库,是一种封装机制,简单说把所有的源代码编译成目标代码后打成的包。
那么用户怎么能知道这个库提供什么样的接口呢?难道要用nm等工具逐个扫描?
不用担心,库的开发者早以把一切都做好了。除了包含目标代码的库外,www.Linuxidc.com一般还会提供一系列的头文件,头文件中就包含了库的接口。为了让方便用户,再加上一个使用说明就差不多完美了。
2 库的分类
2.1 库的分类
根据链接时期的不同,库又有静态库和动态库之分。
(1) 静态库
一般以*.a命名。
程序编译时被加载,此后,只要程序不被重新编译,静态库就没有作用了(可以删掉)。
由于静态库的代码在编译过程中已经被载入可执行程序,因此体积较大,如果有多个应用程序都用了同一个静态库,在存放可执行程序的硬盘中就会有这个静态库的多份拷贝。如果他们同时在运行,那么在内存中也会有这个静态库的多份拷贝。但是如下面提到的动态库相比较,程序执行时间比较短,因为没有执行时库函数的加载。所谓“以空间换时间”。
下面我们用一个实例说明静态库的编程和使用。
//库函数:hellowlib.c
#include
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)两种。
动态链接(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, 缓存文件内容可能改变,所需的动态链接库可能不被系统共享了。
5)编译时指定库的加载路径: gcc -o testlib testlib.c -L ./ -lhello -Wl, -rpath ./
这里, -rpath 说明了程序运行时库的加载路径,因为-rpath是ld命令的选项,所以gcc调用它时需要使用gcc的 -Wl选项。
动态加载(Dynamic loading)
程序不会自动加载库,需要在程序中由程序员指定何时加载。系统提供了一套动态加载API以方便我们使用.
先看一下这些API:
1)
#include
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关闭打开的动态库,防止资源泄露。
(3)静态库和动态库的比较
链接静态库其实从某种意义上来说也是一种粘贴复制,只不过它操作的对象是目标代码而不是源码而已。因为静态库被链接后库就直接嵌入可执行文件中了,这样就带来了两个问题。
首先就是系统空间被浪费了。这是显而易见的,想象一下,如果多个程序链接了同一个库,则每一个生成的可执行文件就都会有一个库的副本,必然会浪费系统空间。
再者,人非圣贤,即使是精心调试的库,也难免会有错。一旦发现了库中有bug,挽救起来就比较麻烦了。必须一一把链接该库的程序找出来,然后重新编译。
而动态库的出现正弥补了静态库的以上弊端。因为动态库是在程序运行时被链接的,所以磁盘上只须保留一份副本,因此节约了磁盘空间。如果发现了bug或要升级也很简单,只要用新的库把原来的替换掉就行了。
那么,是不是静态库就一无是处了呢?
答曰:非也非也。不是有句话么:存在即是合理。静态库既然没有湮没在滔滔的历史长河中,就必然有它的用武之地。想象一下这样的情况:如果你用libpcap库编了一个程序,要给被人运行,而他的系统上没有装pcap库,该怎么解决呢?最简单的办法就是编译该程序时把所有要链接的库都链接它们的静态库,这样,就可以在别人的系统上直接运行该程序了。
所谓有得必有失,正因为动态库在程序运行时被链接,故程序的运行速度和链接静态库的版本相比必然会打折扣。然而瑕不掩瑜,动态库的不足相对于它带来的好处在现今硬件下简直是微不足道的,所以链接程序在链接时一般是优先链接动态库的,除非用-static参数指定链接静态库。
(4)如何判断一个程序有没有链接动态库?
答案是用file实用程序。
file程序是用来判断文件类型的,在file命令下,所有文件都会原形毕露的。
顺便说一个技巧。有时在 windows下用浏览器下载tar.gz或tar.bz2文件,后缀名会变成奇怪的tar.tar,到Linux有些新手就不知怎么解压了。但 Linux下的文件类型并不受文件后缀名的影响,所以我们可以先用命令file xxx.tar.tar看一下文件类型,然后用tar加适当的参数解压。
另外,还可以借助程序ldd实用程序来判断。
ldd是用来打印目标程序(由命令行参数指定)所链接的所有动态库的信息的,如果目标程序没有链接动态库,则打印“not a dynamic executable”,ldd的用法请参考manpage。
3 创建自己的库
下面是testdyn.c程序实例:
#include
#include
#include
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参数。
程序运行:
[[email protected] fengxz]# ./testdyn
hellow,now in lib routine
load share library success!
以hello.c为例
创建文件hello.c,内容如下:
#include
void hello(void)
{
printf("Hello World\n");
}
用命令gcc -shared hello.c -o libhello.so编译为动态库。可以看到,当前目录下多了一个文件libhello.so。
[[email protected] test]$ file libhello.so
libhello.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped
看到了吧,文件类型是shared object了。
再编辑一个测试文件test.c,内容如下:
int
main()
{
hello();
return 0;
}
这下可以编译了:)
[[email protected] test]$ gcc test.c
/tmp/ccm7w6Mn.o: In function `main‘:
test.c:(.text+0x1d): undefined reference to `hello‘
collect2: ld returned 1 exit status
链接时gcc找不到hello函数,编译失败:(。原因是hello在我们自己创建的库中,如果gcc能找到那才教见鬼呢!ok,再接再厉。
[[email protected] test]$ gcc test.c -lhello
/usr/lib/gcc/i686-pc-Linux-gnu/4.0.0/../../../../i686-pc-Linux-gnu/bin/ld: cannot find -lhello
collect2: ld returned 1 exit status
[[email protected] test]$ gcc test.c -lhello -L.
[[email protected] test]$
第一次编译直接编译,gcc默认会链接标准c库,但符号名hello解析不出来,故连接阶段通不过了。
现在用gcc test.c -lhello -L.已经编译成功了,默认输出为a.out。现在来试着运行一下:
[[email protected] test]$ ./a.out
./a.out: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
咦,怎么回事?原来虽然链接时链接器(dynamic linker)找到了动态库libhello.so,但动态加载器(dynamic loader, 一般是/lib/ld-Linux.so.2)却没找到。再来看看ldd的输出:
[[email protected] test]$ ldd a.out
Linux-gate.so.1 => (0xffffe000)
libhello.so => not found
libc.so.6 => /lib/libc.so.6 (0x40034000)
/lib/ld-Linux.so.2 (0x40000000)
果然如此,看到没有,libhello.so => not found。
Linux为我们提供了两种解决方法:
1.可以把当前路径加入 /etc/ld.so.conf中然后运行ldconfig,或者以当前路径为参数运行ldconfig(要有root权限才行)。
2.把当前路径加入环境变量LD_LIBRARY_PATH中
当然,如果你觉得不会引起混乱的话,可以直接把该库拷入/lib,/usr/lib/等位置(无可避免,这样做也要有权限),这样链接器和加载器就都可以准确的找到该库了。
我们采用第二种方法:
[[email protected] test]$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PA
[[email protected] test]$ ldd a.out
Linux-gate.so.1 => (0xffffe000)
libhello.so => ./libhello.so (0x4001f000)
libc.so.6 => /lib/libc.so.6 (0x40036000)
/lib/ld-Linux.so.2 (0x40000000)
哈哈,这下ld-Linux.so.2就可以找到libhello.so这个库了。
现在可以直接运行了:
[[email protected] test]$ ./a.out
Hello World
3.2 创建静态库
仍使用刚才的hello.c和test.c。
第一步,生成目标文件。
[[email protected] test]$ gcc -c hello.c
[[email protected] test]$ ls hello.o -l
-rw-r--r-- 1 leo users 840 5月 6 12:48 hello.o
第二步,把目标文件归档。
[[email protected] test]$ ar r libhello.a hello.o
ar: creating libhello.a
OK,libhello.a就是我们所创建的静态库了,简单吧:)
[[email protected] test]$ file libhello.a
libhello.a: current ar archive
下面一行命令就是教你如何在程序中链接静态库的:
[[email protected] test]$ gcc test.c -lhello -L. -static -o hello.static
我们来用file命令比较一下用动态库和静态库链接的程序的区别:
[[email protected] test]$ gcc test.c -lhello -L. -o hello.dynamic
正如前面所说,链接器默认会链接动态库(这里是libhello.so),所以只要把上个命令中的 -static参数去掉就可以了。
用file实用程序验证一下是否按我们的要求生成了可执行文件:
[[email protected] test]$ file hello.static hello.dynamic
hello.static: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.6, statically linked, not stripped
hello.dynamic: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.6, dynamically linked (uses shared libs), not stripped
不妨顺便练习一下ldd的用法:
[[email protected] test]$ ldd hello.static hello.dynamic
hello.static:
not a dynamic executable
hello.dynamic:
Linux-gate.so.1 => (0xffffe000)
libhello.so => ./libhello.so (0x4001f000)
libc.so.6 => /lib/libc.so.6 (0x40034000)
/lib/ld-Linux.so.2 (0x40000000)
OK,看来没有问题,那就比较一下大小先:
[[email protected] test]$ ls -l hello.[ds]*
-rwxr-xr-x 1 leo users 5911 5月 6 12:54 hello.dynamic
-rwxr-xr-x 1 leo users 628182 5月 6 12:54 hello.static
看到区别了吧,链接静态库的目标程序和链接动态库的程序比起来简直就是一个庞然大物!
这么小的程序,很难看出执行时间的差别,不过为了完整起见,还是看一下time的输出吧:
[[email protected] test]$ time ./hello.static
Hello World
real 0m0.001s
user 0m0.000s
sys 0m0.001s
[[email protected] test]$ time ./hello.dynamic
Hello World
real 0m0.001s
user 0m0.000s
sys 0m0.001s
如果程序比较大的话,应该效果会很明显的。