Linux下C语言函数库概述

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

总体概述,C语言的函数库可以有三种使用的形式:静态、共享和动态。

其中静态库的代码在编译时就已连接到开发人员开发的应用程序中。

而共享库只是在程序开始运行时才载入,在编译时, 只是简单地指定需要使用的库函数就可以了。

动态库则是共享库的另一种变化形式,它也是在程序运行时载入,但与共享库不同的是, 使用的库函数不是在程序运行开始,而是在程序中的语句需要使用该函数时才载入,动态库可以在程序运行期间释放动态库所占用的内存,腾出空间供其它程序使用。

由于共享库和动态库并没有在程序中包括库函数的内容,只是包含了对库函数的引用,因此代码的规模比较小。

Linux下的库文件分为共享库和静态库两大类,它们两者的差别仅在程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。

静态函数库:每次当应用程序和静态连接的函数库一起编译时,任何引用的库函数中的代码都会被直接包含进最终的二进制程序。

共享函数库:包含每个库函数的单一全局版本,它在所有应用程序之间共享。

这一过程背后所涉及的机制相当复杂,但主要依靠的是现代计算机的虚拟内存能力,它允许包含库函数的物理内存安全地在多个独立用户程序之间共享。

区分库类型最好的方法是看它们的文件后缀,通常共享库以.so(Shared Object的缩写)结尾,静态链接库通常以.a结尾(Archive的缩写)。

在终端缺省情况下,共享库通常为绿色,而静态库为黑色。

已经开发的大多数库都采取共享库的方式,Linux系统中目前可执行文件的标准格式为ELF(Executable and Linkable Format)格式。

ELF格式的可执行文件使得共享库能够比较容易地实现:
.a的是为了支持较老的a.out格式的可执行文件,静态库文件, 可以用ar 命令生成。

.so的是支持elf格式的可执行文件的库,动态库文件,编译时加上指定的选项即可生成。

在linux系统中可用的库都存放在/usr/lib和/lib目录中。

命名规则
GNU库的使用必须遵守Library GNU Public License(LGPL许可协议)。

该协议与GNU许可协议略有不同, 开发人员可以免费使用GNU库进行软件开发, 但必须保证向用户提供所用的库函数的源代码。

库文件名由前缀lib和库名以及后缀组成,根据库的类型不同,后缀名也不一样。

共享库的后缀名由.so和版本号组成, 静态库的后缀名为.a。

静态库:libname.a
共享库:libname.so.major.minor.patchlevel
name : 可以是任何字符串, 用来唯一标识某个库,可以是一个或几个字符、甚至一个字母。

major : 主版本号
minor : 次版本号
patchlevel : 补丁版本
在linux中显示一个executable program对库的依赖引用关系的命令是ldd(Library Dependency Display)。

Eg:
[mysql@my101 ~]$ ldd /u01/mysql/bin/mysql
libncursesw.so.5 => /usr/lib/libncursesw.so.5 (0x00845000)
libpthread.so.0 => /lib/tls/libpthread.so.0 (0x00938000)
libmysqlclient.so.16 => /u01/mysql/lib/mysql/libmysqlclient.so.16 (0x00c8d000)
libcrypt.so.1 => /lib/libcrypt.so.1 (0x0066b000)
libnsl.so.1 => /lib/libnsl.so.1 (0x00583000)
libz.so.1 => /usr/lib/libz.so.1 (0x00926000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x009a2000)
libm.so.6 => /lib/tls/libm.so.6 (0x00820000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x00996000)
libc.so.6 => /lib/tls/libc.so.6 (0x006ec000)
/lib/ld-linux.so.2 (0x006cd000)
这里我们特别额外说明下最后一行ld-linux.so库。

当程序被调用的时候, Linux 共享库装载器(动态连接器)也自动被调用,它的作用是保证程序所需要的所有适当版本的库都被调入内存。

而可能的共享库装载器的名字就是ld.so 或者是ld-linux.so (取决于Linux libc 的版本)。

接下来我们以例子的形式来讲解如何用gcc创建静态库和动态共享库。

创建静态库
$ cat base.c
void prt(){
printf("this is the base test\n");
}
$ cat st.c
#include <stdio.h>
int main(){
prt();
return 0;
}
$ gcc -c base.c -o base.o
$ ar cqs libbase.a base.o
使用ar命令来生成静态库文件。

Gcc使用 -static 选项来生成静态执行执行文件,也就是使用该选项,它会把静态库文件(如果c程序中有引用的话)的内容复制到相应的c文件中,一起编译生产可执行文件。

$ gcc st.c -static base.o -o st2 -- 直接使用base.o生产静态执行文件
$ gcc st.c -static -L. -lbase -o st3 -- 通过L指定目录,-l指定静态库文件名称(libbase.a)来生成静态执行文件
$ gcc st.c -static libbase.a -o st4 -- 直接通过指定静态库文件详细名来生成静态执行文件
$ gcc st.c libbase.a -o st1 -- 这种情况其实生成的还是动态执行文件,但是却使用了静态库libbase.a内容,这和前面的方法有什么区别呢,下面再议。

我们下面来看下上面几种文件的区别,先看大小
$ls -lthc
total 2.1M
-rwxr-xr-x 1 mysql dba 504K Aug 31 17:41 st2
-rwxr-xr-x 1 mysql dba 504K Aug 31 17:36 st4
-rwxr-xr-x 1 mysql dba 504K Aug 31 17:36 st3
-rwxr-xr-x 1 mysql dba 6.8K Aug 31 17:34 st1
-rw-r--r-- 1 mysql dba 1.7K Aug 31 17:34 libbase.a
-rw-r--r-- 1 mysql dba 1.5K Aug 31 17:32 base.o
-rw-r--r-- 1 mysql dba 70 Aug 31 17:25 st.c
-rw-r--r-- 1 mysql dba 58 Aug 31 17:25 base.c
-rw-r--r-- 1 mysql dba 9.5K Aug 31 13:33 a.sql
-rw-r--r-- 1 mysql dba 922 Aug 30 22:24 a.c
可以使用ldd命令,查看执行文件的库函数引用情况
$ldd st1
libc.so.6 => /lib64/tls/libc.so.6 (0x0000003848b00000)
/lib64/ld-linux-x86-64.so.2 (0x0000003848700000)
$ldd st2
not a dynamic executable
$ldd st3
not a dynamic executable
$ldd st4
not a dynamic executable
使用-static 选项,它的作用是禁止使用动态库,因此它编译出来的东西一般都比较大,无需任何动态链接库都可以运行。

如果不使用这个选项,如我们上面的最后一种情况,它其实生成的是动态执行文件,只是其中一起编译的动态库文件libbase.a在编译的时候被静态到要编译的文件st.c中去了,而其他部分(如printf函数)还是使用动态库文件的。

创建共享库
$ cat sh.c
#include <stdio.h>
int main()
{
prt();
return 0;
}
$ gcc -fPIC -c base.c -o base.o
-fPIC参数标记告诉gcc产生的代码不要包含对函数和变量具体内存位置的引用,这是因为现在还无法知道使用该消息代码的应用程序会将它连接到哪一段内存地址空间。

这样,编译输出的文件base.o可以被用于建立共享函数库,下面只需使用gcc的“-shared”参数来申明编译为共享库即可:
$ gcc -shared base.o -o libbase.so
然后编译sh.c的时候,我们需要更多的参数让gcc知道如何寻找共享库:
$ gcc -L. -lbase sh.c -o sh
-L参数指定到哪个附加路径下面去寻找共享库,现在我们指定在当前目录下面寻找
-l参数指定链接到哪个共享库上面,我们传的参数base,那么gcc就会自动链接到
libbase.so这个共享库上面
下面开始运行连接后的可执行文件:
$ ./sh
./sh: error while loading shared libraries: libbase.so: cannot open shared object file: No such file or directory
发现出错,这是因为上面在做连接编译的时候,虽然通过-L和-l指定了连接共享库路径,从而sh.c文件能编译成功为可执行文件sh。

但是当你执行sh时,由于没有指定该文件所引用的共享库地址,那么ld.so就找不到相应的共享库libbase.so,于是也就无法运行成功了。

那么,如何告知共享库装载器ld.so,已经编译好的库libbase.so的位置呢?一共有几种方法来指定?每种方法的优先级是什么?这就是我们后面要讨论的Linux下共享库文件搜索路径的问题,请看下回分解。

Linux动态库文件搜索路径
首先回答前面的问题,一共有多少种方法来指定告诉linux共享库链接器ld.so已经编译好的库libbase.so的位置呢?答案是一共有五种,它们都可以通知ld.so去哪些地方找下已经编译好的c语言函数动态库,它们是:
1)ELF可执行文件中动态段中DT_RPATH所指定的路径。

即在编译目标代码时, 对gcc 加入链接参数“-Wl,-rpath”指定动态库搜索路径,eg:gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c
2)环境变量LD_LIBRARY_PATH 指定的动态库搜索路径
3)/etc/ld.so.cache中所缓存的动态库路径,这个可以通过先修改配置文件/etc/ld.so.conf中指定的动态库搜索路径,然后执行ldconfig命令来改变。

4)默认的动态库搜索路径/lib
5)默认的动态库搜索路径/usr/lib
另外:在嵌入式Linux系统的实际应用中,1和2被经常使用,也有一些相对简单的的嵌入式系统会采用4或5的路径来规范动态库,3在嵌入式系统中使用的比较少, 因为有很多系统根本就不支持ld.so.cache。

那么,动态链接器ld.so在这五种路径中,是按照什么样的顺序来搜索需要的动态共享库呢?答案这里先告知就是按照上面的顺序来得,即优先级是:1-->2-->3-->4-->5。

我们可以写简单的程序来证明这个结论。

首先,写成5个函数,这5个函数名称都叫pt,但是里面的内容不一样:
pt1.c
#include <stdio.h>
void pt(){
printf("1 path on the gcc give \n");
}
pt2.c
#include <stdio.h>
void pt(){
printf("2 path on the LD_LIBRARY_PATH \n");
}
pt3.c
#include <stdio.h>
void pt(){
printf("3 path on the /etc/ld.so.conf \n");
}
pt4.c
#include <stdio.h>
void pt(){
printf("4 path on the /lib \n");
}
pt5.c
#include <stdio.h>
void pt(){
printf("5 path on the /usr/lib \n");
}
然后,分别编译这5个函数,然后将它们分别移到上面5种情况对应的5个不同目录下:gcc -fPIC -c pt1.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /tmp/st/1/
gcc -fPIC -c pt2.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /tmp/st/2/
gcc -fPIC -c pt3.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /tmp/st/3/
gcc -fPIC -c pt4.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /lib/
gcc -fPIC -c pt5.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /usr/lib/
再次,编写一个main函数m,让它来调用函数pt:
m.c
#include <stdio.h>
/*void pt();*/
int main(){
printf("start....\n");
pt();
printf("......end\n");
return 0;
}
最后,准备环境,让ld都知道这5个路径:
(a) 往/etc/ld.so.conf总增加一行,内容:/tmp/st/3,然后执行 ldconfig 命令
(b) export LD_LIBRARY_PATH=/tmp/st/2
另外3中路径,ld都可以得到,请接着看下面。

之后测试:
gcc m.c -o m1 -L/tmp/st/1 -lpt -Wl,-rpath,/tmp/st/1
./m1
start....
1 path on the gcc give
......end
这里在可执行文件中动态段中DT_RPATH所指定的路径,因此需要在编译m.c的时候就指定路径,由于其他路径都也告诉了ld,很明显,此种方法优先级最高了。

gcc m.c -o m -L/tmp/st/1 -lpt
./m
start....
2 path on the LD_LIBRARY_PATH
......end
这里很显然调用了LD_LIBRARY_PATH指定了路径中的共享库,因此此种情况优先级第二。

mv /tmp/st/2/libpt.so /tmp/st/2/libpt2.so
/m
start....
3 path on the /etc/ld.so.conf
......end
这里是调用了/etc/ld.so.cache中所缓存的动态库路径中的共享库,因此此种情况优先级第三。

mv /tmp/st/3/libpt.so /tmp/st/3/libpt3.so
./m
start....
4 path on the /lib
......end
这里是调用/lib中的共享库,优先级第四。

rm /lib/libpt.so
./m
start....
5 path on the /usr/lib
......end
这里是调用/lib中的共享库,优先级第五。

故证明这五种路径指定方法的优先级是1-->2-->3-->4-->5!。

相关文档
最新文档