2024年10月26日发(作者:宫新林)
总体概述,C语言的函数库可以有三种使用的形式:静态、共享和动态。
其中静态库的代码在编译时就已连接到开发人员开发的应用程序中。
而共享库只是在程序开始运行时才载入,在编译时, 只是简单地指定需要使用的库函数
就可以了。
动态库则是共享库的另一种变化形式,它也是在程序运行时载入,但与共享库不同的是,
使用的库函数不是在程序运行开始,而是在程序中的语句需要使用该函数时才载入,动态库
可以在程序运行期间释放动态库所占用的内存,腾出空间供其它程序使用。
由于共享库和动态库并没有在程序中包括库函数的内容,只是包含了对库函数的引用,
因此代码的规模比较小。
Linux下的库文件分为共享库和静态库两大类,它们两者的差别仅在程序执行时所需的
代码是在运行时动态加载的,还是在编译时静态加载的。
静态函数库:每次当应用程序和静态连接的函数库一起编译时,任何引用的库函数中的
代码都会被直接包含进最终的二进制程序。
共享函数库:包含每个库函数的单一全局版本,它在所有应用程序之间共享。这一过程
背后所涉及的机制相当复杂,但主要依靠的是现代计算机的虚拟内存能力,它允许包含库函
数的物理内存安全地在多个独立用户程序之间共享。
区分库类型最好的方法是看它们的文件后缀,通常共享库以.so(Shared Object的缩写)
结尾,静态链接库通常以.a结尾(Archive的缩写)。在终端缺省情况下,共享库通常为绿色,
而静态库为黑色。
已经开发的大多数库都采取共享库的方式,Linux系统中目前可执行文件的标准格式为
ELF(Executable and Linkable Format)格式。ELF格式的可执行文件使得共享库能够比较容易
地实现:
.a的是为了支持较老的格式的可执行文件,静态库文件, 可以用ar 命令生成。
.so的是支持elf格式的可执行文件的库,动态库文件,编译时加上指定的选项即可生成。
在linux系统中可用的库都存放在/usr/lib和/lib目录中。
命名规则
GNU库的使用必须遵守Library GNU Public License(LGPL许可协议)。该协议与GNU许可
协议略有不同, 开发人员可以免费使用GNU库进行软件开发, 但必须保证向用户提供所用
的库函数的源代码。
库文件名由前缀lib和库名以及后缀组成,根据库的类型不同,后缀名也不一样。共享
库的后缀名由.so和版本号组成, 静态库的后缀名为.a。
静态库:libname.a
共享库:evel
name : 可以是任何字符串, 用来唯一标识某个库,可以是一个或几个字符、甚至一个字母。
major : 主版本号
minor : 次版本号
patchlevel : 补丁版本
在linux中显示一个executable program对库的依赖引用关系的命令是ldd(Library
Dependency Display)。Eg:
[mysql@my101 ~]$ ldd /u01/mysql/bin/mysql
.5 => /usr/lib/.5 (0x00845000)
.0 => /lib/tls/.0 (0x00938000)
.16 => /u01/mysql/lib/mysql/.16 (0x00c8d000)
.1 => /lib/.1 (0x0066b000)
.1 => /lib/.1 (0x00583000)
.1 => /usr/lib/.1 (0x00926000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x009a2000)
.6 => /lib/tls/.6 (0x00820000)
libgcc_.1 => /lib/libgcc_.1 (0x00996000)
.6 => /lib/tls/.6 (0x006ec000)
/lib/.2 (0x006cd000)
这里我们特别额外说明下最后一行库。当程序被调用的时候, Linux 共享库装载器
(动态连接器)也自动被调用,它的作用是保证程序所需要的所有适当版本的库都被调入内
存。而可能的共享库装载器的名字就是 或者是 (取决于 Linux libc 的版本)。
接下来我们以例子的形式来讲解如何用gcc创建静态库和动态共享库。
创建静态库
$ cat base.c
void prt(){
printf("this is the base testn");
}
$ cat st.c
#include
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
-rw-r--r-- 1 mysql dba 922 Aug 30 22:24 a.c
可以使用ldd命令,查看执行文件的库函数引用情况
$ldd st1
.6 => /lib64/tls/.6 (0xb00000)
/lib64/.2 (0x0000)
$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
int main()
{
prt();
return 0;
}
$ gcc -fPIC -c base.c -o base.o
-fPIC参数标记告诉gcc产生的代码不要包含对函数和变量具体内存位置的引用,这
是因为现在还无法知道使用该消息代码的应用程序会将它连接到哪一段内存地址空间。这样,
编译输出的文件base.o可以被用于建立共享函数库,下面只需使用gcc的“-shared”
参数来申明编译为共享库即可:
$ gcc -shared base.o -o
然后编译sh.c的时候,我们需要更多的参数让gcc知道如何寻找共享库:
$ gcc -L. -lbase sh.c -o sh
-L参数指定到哪个附加路径下面去寻找共享库,现在我们指定在当前目录下面寻找
-l参数指定链接到哪个共享库上面,我们传的参数base,那么gcc就会自动链接到
这个共享库上面
下面开始运行连接后的可执行文件:
$ ./sh
./sh: error while loading shared libraries: : cannot open
shared object file: No such file or directory
发现出错,这是因为上面在做连接编译的时候,虽然通过-L和-l指定了连接共享库路径,
从而sh.c文件能编译成功为可执行文件sh。但是当你执行sh时,由于没有指定该文件所引
用的共享库地址,那么就找不到相应的共享库,于是也就无法运行成功了。
那么,如何告知共享库装载器,已经编译好的库的位置呢?一共有几种
方法来指定?每种方法的优先级是什么?这就是我们后面要讨论的Linux下共享库文件搜索
路径的问题,请看下回分解。
Linux动态库文件搜索路径
首先回答前面的问题,一共有多少种方法来指定告诉linux共享库链接器已经编
译好的库的位置呢?答案是一共有五种,它们都可以通知去哪些地方找
下已经编译好的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/中所缓存的动态库路径,这个可以通过先修改配置文件
/etc/中指定的动态库搜索路径,然后执行ldconfig命令来改变。
4)默认的动态库搜索路径/lib
5)默认的动态库搜索路径/usr/lib
另外:在嵌入式Linux系统的实际应用中,1和2被经常使用,也有一些相对简单的的
嵌入式系统会采用4或5的路径来规范动态库,3在嵌入式系统中使用的比较少, 因为有很
多系统根本就不支持。
那么,动态链接器在这五种路径中,是按照什么样的顺序来搜索需要的动态共享
库呢?答案这里先告知就是按照上面的顺序来得,即优先级是:1-->2-->3-->4-->5。我们
可以写简单的程序来证明这个结论。
首先,写成5个函数,这5个函数名称都叫pt,但是里面的内容不一样:
pt1.c
#include
void pt(){
printf("1 path on the gcc give n");
}
pt2.c
#include
void pt(){
printf("2 path on the LD_LIBRARY_PATH n");
}
pt3.c
#include
void pt(){
printf("3 path on the /etc/ n");
}
pt4.c
#include
void pt(){
printf("4 path on the /lib n");
}
pt5.c
#include
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
mv /tmp/st/1/
gcc -fPIC -c pt2.c -o pt.o
gcc -shared pt.o -o
mv /tmp/st/2/
gcc -fPIC -c pt3.c -o pt.o
gcc -shared pt.o -o
mv /tmp/st/3/
gcc -fPIC -c pt4.c -o pt.o
gcc -shared pt.o -o
mv /lib/
gcc -fPIC -c pt5.c -o pt.o
gcc -shared pt.o -o
mv /usr/lib/
再次,编写一个main函数m,让它来调用函数pt:
m.c
#include
/*void pt();*/
int main(){
printf("");
pt();
printf("......endn");
return 0;
}
最后,准备环境,让ld都知道这5个路径:
(a) 往/etc/总增加一行,内容:/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
1 path on the gcc give
......end
这里在可执行文件中动态段中DT_RPATH所指定的路径,因此需要在编译m.c的时候就指定
路径,由于其他路径都也告诉了ld,很明显,此种方法优先级最高了。
gcc m.c -o m -L/tmp/st/1 -lpt
./m
2 path on the LD_LIBRARY_PATH
......end
这里很显然调用了LD_LIBRARY_PATH指定了路径中的共享库,因此此种情况优先级第二。
mv /tmp/st/2/ /tmp/st/2/
/m
3 path on the /etc/
......end
这里是调用了/etc/中所缓存的动态库路径中的共享库,因此此种情况优先级
第三。
mv /tmp/st/3/ /tmp/st/3/
./m
4 path on the /lib
......end
这里是调用/lib中的共享库,优先级第四。
rm /lib/
./m
5 path on the /usr/lib
......end
这里是调用/lib中的共享库,优先级第五。
故证明这五种路径指定方法的优先级是1-->2-->3-->4-->5!
2024年10月26日发(作者:宫新林)
总体概述,C语言的函数库可以有三种使用的形式:静态、共享和动态。
其中静态库的代码在编译时就已连接到开发人员开发的应用程序中。
而共享库只是在程序开始运行时才载入,在编译时, 只是简单地指定需要使用的库函数
就可以了。
动态库则是共享库的另一种变化形式,它也是在程序运行时载入,但与共享库不同的是,
使用的库函数不是在程序运行开始,而是在程序中的语句需要使用该函数时才载入,动态库
可以在程序运行期间释放动态库所占用的内存,腾出空间供其它程序使用。
由于共享库和动态库并没有在程序中包括库函数的内容,只是包含了对库函数的引用,
因此代码的规模比较小。
Linux下的库文件分为共享库和静态库两大类,它们两者的差别仅在程序执行时所需的
代码是在运行时动态加载的,还是在编译时静态加载的。
静态函数库:每次当应用程序和静态连接的函数库一起编译时,任何引用的库函数中的
代码都会被直接包含进最终的二进制程序。
共享函数库:包含每个库函数的单一全局版本,它在所有应用程序之间共享。这一过程
背后所涉及的机制相当复杂,但主要依靠的是现代计算机的虚拟内存能力,它允许包含库函
数的物理内存安全地在多个独立用户程序之间共享。
区分库类型最好的方法是看它们的文件后缀,通常共享库以.so(Shared Object的缩写)
结尾,静态链接库通常以.a结尾(Archive的缩写)。在终端缺省情况下,共享库通常为绿色,
而静态库为黑色。
已经开发的大多数库都采取共享库的方式,Linux系统中目前可执行文件的标准格式为
ELF(Executable and Linkable Format)格式。ELF格式的可执行文件使得共享库能够比较容易
地实现:
.a的是为了支持较老的格式的可执行文件,静态库文件, 可以用ar 命令生成。
.so的是支持elf格式的可执行文件的库,动态库文件,编译时加上指定的选项即可生成。
在linux系统中可用的库都存放在/usr/lib和/lib目录中。
命名规则
GNU库的使用必须遵守Library GNU Public License(LGPL许可协议)。该协议与GNU许可
协议略有不同, 开发人员可以免费使用GNU库进行软件开发, 但必须保证向用户提供所用
的库函数的源代码。
库文件名由前缀lib和库名以及后缀组成,根据库的类型不同,后缀名也不一样。共享
库的后缀名由.so和版本号组成, 静态库的后缀名为.a。
静态库:libname.a
共享库:evel
name : 可以是任何字符串, 用来唯一标识某个库,可以是一个或几个字符、甚至一个字母。
major : 主版本号
minor : 次版本号
patchlevel : 补丁版本
在linux中显示一个executable program对库的依赖引用关系的命令是ldd(Library
Dependency Display)。Eg:
[mysql@my101 ~]$ ldd /u01/mysql/bin/mysql
.5 => /usr/lib/.5 (0x00845000)
.0 => /lib/tls/.0 (0x00938000)
.16 => /u01/mysql/lib/mysql/.16 (0x00c8d000)
.1 => /lib/.1 (0x0066b000)
.1 => /lib/.1 (0x00583000)
.1 => /usr/lib/.1 (0x00926000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x009a2000)
.6 => /lib/tls/.6 (0x00820000)
libgcc_.1 => /lib/libgcc_.1 (0x00996000)
.6 => /lib/tls/.6 (0x006ec000)
/lib/.2 (0x006cd000)
这里我们特别额外说明下最后一行库。当程序被调用的时候, Linux 共享库装载器
(动态连接器)也自动被调用,它的作用是保证程序所需要的所有适当版本的库都被调入内
存。而可能的共享库装载器的名字就是 或者是 (取决于 Linux libc 的版本)。
接下来我们以例子的形式来讲解如何用gcc创建静态库和动态共享库。
创建静态库
$ cat base.c
void prt(){
printf("this is the base testn");
}
$ cat st.c
#include
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
-rw-r--r-- 1 mysql dba 922 Aug 30 22:24 a.c
可以使用ldd命令,查看执行文件的库函数引用情况
$ldd st1
.6 => /lib64/tls/.6 (0xb00000)
/lib64/.2 (0x0000)
$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
int main()
{
prt();
return 0;
}
$ gcc -fPIC -c base.c -o base.o
-fPIC参数标记告诉gcc产生的代码不要包含对函数和变量具体内存位置的引用,这
是因为现在还无法知道使用该消息代码的应用程序会将它连接到哪一段内存地址空间。这样,
编译输出的文件base.o可以被用于建立共享函数库,下面只需使用gcc的“-shared”
参数来申明编译为共享库即可:
$ gcc -shared base.o -o
然后编译sh.c的时候,我们需要更多的参数让gcc知道如何寻找共享库:
$ gcc -L. -lbase sh.c -o sh
-L参数指定到哪个附加路径下面去寻找共享库,现在我们指定在当前目录下面寻找
-l参数指定链接到哪个共享库上面,我们传的参数base,那么gcc就会自动链接到
这个共享库上面
下面开始运行连接后的可执行文件:
$ ./sh
./sh: error while loading shared libraries: : cannot open
shared object file: No such file or directory
发现出错,这是因为上面在做连接编译的时候,虽然通过-L和-l指定了连接共享库路径,
从而sh.c文件能编译成功为可执行文件sh。但是当你执行sh时,由于没有指定该文件所引
用的共享库地址,那么就找不到相应的共享库,于是也就无法运行成功了。
那么,如何告知共享库装载器,已经编译好的库的位置呢?一共有几种
方法来指定?每种方法的优先级是什么?这就是我们后面要讨论的Linux下共享库文件搜索
路径的问题,请看下回分解。
Linux动态库文件搜索路径
首先回答前面的问题,一共有多少种方法来指定告诉linux共享库链接器已经编
译好的库的位置呢?答案是一共有五种,它们都可以通知去哪些地方找
下已经编译好的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/中所缓存的动态库路径,这个可以通过先修改配置文件
/etc/中指定的动态库搜索路径,然后执行ldconfig命令来改变。
4)默认的动态库搜索路径/lib
5)默认的动态库搜索路径/usr/lib
另外:在嵌入式Linux系统的实际应用中,1和2被经常使用,也有一些相对简单的的
嵌入式系统会采用4或5的路径来规范动态库,3在嵌入式系统中使用的比较少, 因为有很
多系统根本就不支持。
那么,动态链接器在这五种路径中,是按照什么样的顺序来搜索需要的动态共享
库呢?答案这里先告知就是按照上面的顺序来得,即优先级是:1-->2-->3-->4-->5。我们
可以写简单的程序来证明这个结论。
首先,写成5个函数,这5个函数名称都叫pt,但是里面的内容不一样:
pt1.c
#include
void pt(){
printf("1 path on the gcc give n");
}
pt2.c
#include
void pt(){
printf("2 path on the LD_LIBRARY_PATH n");
}
pt3.c
#include
void pt(){
printf("3 path on the /etc/ n");
}
pt4.c
#include
void pt(){
printf("4 path on the /lib n");
}
pt5.c
#include
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
mv /tmp/st/1/
gcc -fPIC -c pt2.c -o pt.o
gcc -shared pt.o -o
mv /tmp/st/2/
gcc -fPIC -c pt3.c -o pt.o
gcc -shared pt.o -o
mv /tmp/st/3/
gcc -fPIC -c pt4.c -o pt.o
gcc -shared pt.o -o
mv /lib/
gcc -fPIC -c pt5.c -o pt.o
gcc -shared pt.o -o
mv /usr/lib/
再次,编写一个main函数m,让它来调用函数pt:
m.c
#include
/*void pt();*/
int main(){
printf("");
pt();
printf("......endn");
return 0;
}
最后,准备环境,让ld都知道这5个路径:
(a) 往/etc/总增加一行,内容:/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
1 path on the gcc give
......end
这里在可执行文件中动态段中DT_RPATH所指定的路径,因此需要在编译m.c的时候就指定
路径,由于其他路径都也告诉了ld,很明显,此种方法优先级最高了。
gcc m.c -o m -L/tmp/st/1 -lpt
./m
2 path on the LD_LIBRARY_PATH
......end
这里很显然调用了LD_LIBRARY_PATH指定了路径中的共享库,因此此种情况优先级第二。
mv /tmp/st/2/ /tmp/st/2/
/m
3 path on the /etc/
......end
这里是调用了/etc/中所缓存的动态库路径中的共享库,因此此种情况优先级
第三。
mv /tmp/st/3/ /tmp/st/3/
./m
4 path on the /lib
......end
这里是调用/lib中的共享库,优先级第四。
rm /lib/
./m
5 path on the /usr/lib
......end
这里是调用/lib中的共享库,优先级第五。
故证明这五种路径指定方法的优先级是1-->2-->3-->4-->5!