回顾一下我们之前用数学函数是如何用的:
①在用到数学函数的源代码文件里,#include<math.h>,把数学函数的声明加进来,方便编译器进行类型检查。
②在编译上面的源代码文件时,gcc 结尾添加选项 –lm 把数学函数的库文件,和我们的源代码链接在一起。
③将来就能顺利地执行程序了。
这是使用库函数的典型流程。接下来我们自己创建库文件。并期望像系统的库文件一样使用它。
当人们学习编程时,一开始总是把所有的代码都放到一个函数里。随着水平的进步,他们把代码分别放到几个函数中。在水平继续提高后,他们最终学会了如何用几个文件来构造一个程序。《C专家编程》P201
我们通常把一些功能类似的源代码编译做成库文件。注意:main函数是不能放在库里面的。
以我们之前写过的三个基本排序为例。
有如下文件:insertsort.c bubblesort.c selectsort.c main.c
main.c里使用到了排序函数。
编译时可以这样:gcc *sort.c main.c -o main
若以后希望能像数学函数一样,可以把三个基本排序用在许多不同的程序里。不需要每次使用时,重新编译他们。
编译时可以这样:gcc main.c -o main -lsort
我们把这个三个排序的源代码编译并制作成库文件。
通常相应的头文件和库配套使用,这个头文件称为是库的接口。
我们先为我们即将创建的库,写一个配套的头文件sort.h。
#ifndef _SORT_H #define _SORT_H 1 .../*这里是库里所有没有被static修饰的函数和全局变量的声明*/ #endif |
然后把这个头文件放到系统目录/usr/include中。将来编写用到这些函数的代码时,包含这个头文件#include<sort.h>即可。
若不放到系统目录里,可以使用gcc选项-I ./ 来指定头文件所在的目录
库文件根据链接方式不同,分为静态库和动态库。
用到的命令ar gcc ldconfig ldd
(1)静态库
简单地说,静态库是一些目标文件的简单集合。因此,首先要解决目标文件。
第一步:将各函数代码所在的源文件编译成目标文件。
例如,对于bubblesort.c insertsort.c selectsort.c
gcc -c bubblesort.c insertsort.c selectsort.c
将得到bubblesort.o insertsort.o 和 selectsort.o。
第二步:由ar(archive,归档的意思)把多个目标文件集合起来。
ar -rc libsort.a bubblesort.o insertsort.o selectsort.o
选项:r 替换已经存在的同名文件或插入新的文件到归档文件中。
c 若归档文件不存在,直接创建,不要警告。
通常,静态库的命名方式应遵守libXXXXX.a格式。
应用程序在使用静态库的时候,只需要把命名中的XXXXX部分传递给gcc即可。例如:
gcc 源文件... –o 可执行文件名 -L ./ –lsort
注意:-l 库名最好放在行尾。原因参考《C专家编程》的第100页静态链接的顺序。
若把生成好的库文件放到系统的库目录下/lib/ 或者 /usr/lib/,可以这样:
gcc 源文件... –o 可执行文件名 –lsort
(2)共享库
共享库的构造复杂一些,通常是一个ELF格式的文件。可以有三种方法生成:
ld -G(即 --shared选项)
gcc -shared
libtool
用ld最复杂,用gcc -shared就简单的多,但是-shared并非在任何平台都可以使用。GNU提供了一个更好的工具libtool,专门用来在各种平台上生成各种库。
用gcc的-shared参数:
1、gcc -c -fPIC bubblesort.c selectsort.c insertsort.c
生成三个目标文件bubblesort.o insertsort.o selectsort.o。
选项-fPIC用于生成位置无关的代码,见下面解释。
2、gcc -shared -o libsort.so bubblesort.o insertsort.o selectsort.o
选项-shared 指明生成的是一个共享库文件(.so文件)
这样,就通过bubblesort.o、insertsort.o、selectsort.o生成了共享库文件libsort.so 。
上面两步可以合成一步。
通常,动态库的命名方式应遵守libXXXXX.so格式。
应用程序在使用动态库的时候,只需要把命名中的XXXXX部分传递给gcc即可。例如:
gcc 源文件... –o 可执行文件名 -L ./ –lsort
但运行的时候,却出现找不到库文件的情况。这是因为系统只使用已经被缓存在内存的动态库。下面两种方法都可以让系统把库文件缓存到内存里。
(3)库生成以后的配置
如果要把自己开发的库文件安装到操作系统中,需要有root管理员权限:
(a) 把库文件复制到系统的库目录:
若把自己开发的动态连接库放到系统的库目录下/lib/ 或者 /usr/lib/,可以这样:
gcc 源文件... –o 可执行文件名 –lsort
(b) 若放到其他目录,需要修改相关的系统配置文件:
把该目录的绝对路径写在/etc/ld.so.conf
然后执行/sbin/ldconfig来完成把库文件加载到内存的过程。
系统默认把/usr/lib/、/lib/目录及/etc/ld.so.conf文件记录的目录下的动态库缓存起来。
GCC命令行的几个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的,可能会有性能方面的损失。
-L.:(大写的 l)指定要连接的库在什么目录中。若库文件在/lib或者/usr/lib下,则不需要用-L指定。系统自动去这两个目录查找库文件。
-l 库名:(小写的l)编译器到指定的几个目录下查找为指定库名的库文件。查找连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so或.a来确定库的名称
-I 头文件所在目录:(大写的 i )指定头文件到哪里去找。
LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
分析分别使用两种库文件的可执行文件大小。
静态库和动态库区别
静态库: 使用静态库的程序,链接时,在静态库中寻找所需的函数代码,并把需要的代码集成到可执行程序里。 可执行文件大,运行速度稍快。
| 动态库: 使用动态库的程序,链接时,不集成库文件的代码。只记录符号和动态库的对应关系,和库所在的目录。在运行时,若用到了动态库,再把共享库加载到进程的地址空间里。所以即使连接了的动态库,在运行时没有被调用,也不会带来额外开销。 可执行文件小,运行速度稍慢点。在运行时,需要加载库到进程地址空间。 所有使用同一个动态库的进程,共享同一个动态库拷贝,节省了内存。 动态库使得函数库的版本升级更为容易。新的函数库只要安装到系统,旧的程序就能够自动获得新版本函数库的优点,而无需重新编译。 |
由于动态库的优点,若不指明,编译器默认选择动态库。如/usr/lib/下有libc.a和libc.so,系统优先选择动态库libc.so,若没有动态库,才用静态库。若希望程序只使用静态库,在链接时gcc 后接选项 -static
处理目标文件(包括:.o可从定位目标文件、可执行目标文件、.so共享目标文件)的工具