Linux静态库与动态库
一、简介
实际开发工程中,一般会有很多函数只是声明,而找不到实现的代码,因为那些实现代码已经编译成库了。在Linux系统中.o、.a、*.so文件都是Linux下的程序函数库,即编译好的可供其他程序使用的代码和数据,一般来讲:
- ==.o==:是目标文件,相当于windows中的.obj文件(动态加载函数库);
- ==.so==:为共享库,是shared object,用于动态链接的,和dill差不多(共享函数库);
- ==.a==:为静态库,是好多个.o合在一起,用于静态链接(静态函数库);
- ==.la==:为libtool自动生成的一些共享库,vi编辑查看,主要记录了一些配置信息;
这样做有以下优点:程序模块化,容易重新编译,方便升级。
库文件在链接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路劲是系统中进行设置的,一般Linux把/lib和/usr/lib两个目录作为默认的库搜索路径,所以使用这两个目录中的库时,不需要进行设置搜索路径即可直接使用。
假设当前目录下有这些源文件:main.c func.c func.h,其中main.c要调用func.c中的函数。以下分别是Linux下将func.c生成静态库和动态库的方法,以及如何使用生成的静态库和动态库。
二、静态链接库
2.1 静态库的特点
静态链接库可以将代码进行封装,具有如下特点:
- 静态函数库实际上是简单的普通目标文件(*.o)的集合;
- 静态函数库在可执行程序链接时就加入到可执行代码中,在物理上成为可执行程序的一部分;
- 静态函数库链接生成的程序,在哪里都可以运行,无需该静态函数库的支持;
- 相对于动态函数库链接生成的程序,静态函数库链接生成的可执行程序文件较大;
2.2 静态库的优点
相比源代码来说,静态链接库有如下特点:
- 可以重用以前的程序模块;
- 开发者可以对源代码保密;
- 允许程序员不用重新编译代码而直接把程序link起来,节省了重新编译代码的时间(该优势目前已不明显);
- 理论上将,使用elf格式的静态库函数生成的代码可以比使用共享函数库生成的程序运行速度快。
2.3 静态库的制作
【命令格式】:
在linux环境中,使用ar命令创建静态库文件(创建嵌入式静态库,可使用arm-linux-ar命令),该命令格式如下:
ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files…
arm-linux-ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files…
- ==archive==:定义库的名称;
- ==files== :是库文件中包含的目标文件的清单,用空格分隔每个文件;
【命令选项】:
- -a :把新的目标文件(*.o)添加到静态库文件中现有文件之后;
- -b :把新的目标文件(*.o)添加到静态库文件中现有文件之前;
- -d :从指定的静态库文件中删除文件;
- -m:把文件移动到指定的静态库文件中;
- -p :把静态库文件中指定的文件输出到标准输出;
- -q :快速地把文件追加到静态库文件中;
- -r :把文件插入到静态库文件中;
- -t :显示静态库文件中文件的列表;
- -x :从静态库文件中提取文件;
- -v :使用详细模式
如下面创建了一个libapue.a库文件,然后可以用t选项显示包含在库中的文件:
$ ar –r libapue.a error.o errorlog.o lockreg.o
++创建库文件之后,可以创建这个静态库文件的索引来帮助提高和库连接的其他程序的编译速度。使用ranlib程序创建库的索引,索引存放在库文件的内部。++
$ ranlib libapue.a
++用nm程序可以显示存档文件的索引,它也可以显示目标文件的符号:++
$ nm libapue.a | more
$ nm error.o | more
2.4 静态库的使用
静态库的使用方法,可以采用如下三种中的一种(-static可以不用):
$ gcc test.c –o test libapue.a
$ gcc test.c –o –L. -lfunc –static
$ gcc test.c –o –L./ -lfunc -static
【使用说明】
- 使用静态库生成的可执行文件放在目标板中可以直接运行;
- 使用静态库时,需用-L指定静态库的位置,如上面指定为当前目录:-L./;
- 使用静态库时,需用-l指定静态库的库名,如上面的-lfunc。
【完整使用实例】:
$ gcc func.c –c –o func.o //生成目标文件
$ ar crv libfunc.a func.o //生成静态库
$ gcc main.c –static –L./ –lfunc –o main //使用静态库
$ ./main //执行文件
三、动态链接库
3.1 动态库的简介
动态库是在可执行程序启动时加载到执行程序中,可以被多个执行程序共享使用,使用动态库编译生成的程序相对较小,但运行时需要库文件支持;
动态库的链接时路径和运行时路径:现代链接器在处理动态库时将链接时路径(Link-time path)和运行时路径(Run-time path)分开,用户可通过-L指定链接时,库文件的路径;通过-R(或-rpath)指定程序运行时,库文件的路径,大大提高了库应用的灵活性。比如我们做嵌入式移植时#arm-linux-gcc $(CFLAGS) –o target –L/work/lib/zlib/ -llibz-1.2.3 (work/lib/zlib下是交叉编译好的zlib库),将target编译好后我们只要把zlib库拷贝到开发板的系统默认路径下即可。或者通过- rpath(或-R )、LD_LIBRARY_PATH指定查找路径。
3.2 动态库的命名
每个动态库(*.so)文件都有个特殊的名字,叫做“soname”,它必须以“lib”作为前缀,然后是函数库名,然后是“.so”,最后是版本号信息;不过有个特例,就是非常底层的C库函数都不是以lib开头命名的;每个动态库(*.so)文件都有一个真正的名字,叫做“real name”,它是包含真正库函数代码的文件;真名有一个主版本号和一个发行版本号外,还有一个名字是编译器编译的时候需要的函数库的名字,这个就是简单的soname名字,它不包含任何版本号信息:
- soname:必须的格式:lib+函数库名+.so+版本号信息,如libreadline.so.3
- real name:也就是真正的名字,有主版本号和发行版本号;
- 编译名字:编译器编译的时候需要的函数库的名字就是不包含版本号信息的soname,如上面的libreadlie.so.3去掉后面的“.3”即可。
管理共享函数库的关键是区分好这些名字,++当可执行程序需要在自己的程序中列出这些他们需要的共享库函数的时候,它只要用soname就可以了;反过来,当要创建一个新的共享函数库的时候,只要指定一个特定的文件名,其中包含很细节的版本信息;当安装一个新版本的函数库的时候,只要先将这些函数库文件拷贝到一些特定的目录中,运行ldconfig这个命令即可++(ldconfig检查已存在的库文件,然后创建soname的符号链接到真正的函数库,同时设置/etc/ld.so.cache这个缓冲文件)。
3.3 动态库的制作
由于动态链接库的共享特性,它们不会被拷贝到可执行文件中。在编译的时候,编译器只会做一些函数名之类的检查,在程序运行的时候,被调用的动态链接库函数被安置在内存的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须使用相对地址,而不是绝对地址。那么在编译的时候,我们需要使用地址无关选项(Position Independent Code:PIC)“-fpic”来告诉编译器,这些对象文件是用来做动态链接库的。创建共享库的方法:
$ gcc -fpic error.c -c -o error.o
$ gcc -fpic errorlog.c -c -o errorlog.o
$ gcc -fpic lockreg.c -c -o lockreg.o
$ gcc -shared -o libapue.so error.o errorlog.o lockreg.o
也可以使用如下方法:
$ gcc -shared -fpic error.c errorlog.c lockreg.c -o libapue.so
- -shared选项是告诉编译器这是要建立动态链接库;这与静态链接库的建立很不一样,这里使用的gcc命令而不是ar,同时库的后缀名为*.so;
- -fpic选项告诉编译器该代码为位置无关码,不用此选项的话编译后的代码是位置相关的,所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的;
编译共享库的方法(假设库文件在当前目录),可采用如下三种中的一种:
$ gcc test.c -o test -L. -lapue
$ gcc test.c -o test -L./ -lapue
$ gcc test.c -o test libapue.so
这样就编译出了不包含函数代码的可执行文件了,但是当你运行生成的可执行文件时,动态加载器无法动态加载libapue.so文件。那如何才能让动态加载器发现库文件呢?有如下两种方法:
- 在环境变量LD_LIBRARY_PATH中指明库的搜索路径;
export LD_LIBRARY_PATH=”$(LD_LIBRARY_PATH):.” #添加当前路劲
- 配置/etc/ld.so.conf文件(推荐且简单的方法):一般应用程序的库文件不与系统库文件放在同一个目录下,一般把应用程序的共享库文件放在/usr/local/lib下,新建一个属于自己的目录apue,然后把自己的libapue.so复制过去就行了;同时在/etc/ld.so.conf中新增一行:
/usr/local/lib/apue
以后在编译程序时加上编译选项:-L/usr/local/lib/apue –lapue;针对具体的可执行文件,可以通过ldd命令查看该可执行文件依赖什么共享库:
ldd test
3.4 动态库的使用
【使用说明】
- 使用动态库生成的可执行文件,需将生成的动态库也拷贝到目标板的连接文件目录(/lib或/usr/lib)中,才可以顺利执行。如果将生成的libfunc.so.1.0.0拷贝到系统lib目录(如/usr/lib),则倒数第二步就不需要了。
- 使用动态库链接生成可执行文件时,需要用-L指定动态库的位置,如上面指定为当前目录:-L./。
【使用实例】
$ gcc –fPIC func.c -c –o func.o
$ gcc –shared –o libfunc.so.1.0.0 func.o
$ ln –s libfunc.so.1.0.0 libfunc.so
$ gcc main.c -o main -lfunc -L./
$ export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH
$ ./main
四、程序编译时和运行时库文件路径的指定方法
如何指定程序编译和运行时使用的静态库和动态库呢?在连接时,可以通过-L选项来指定编译时所用到的静态库和动态库文件路径。在程序运行时,可以通过配置/etc/ld.so.conf文件或者使用环境变量LD_LIBRARY_PATH来配置。
4.1 配置/etc/ld.so.conf文件
一般应用程序的库文件不与系统库文件放在同一目录下,一般把应用程序的共享库文件放在/usr/local/lib下,新建一个属于自己的目录apue,然后把刚才生成的libapue.so复制过去,同时在/etc/ld.so.conf中添加库文件的路劲,一行一个,如:
/usr/local/lib/apue
/usr/X11R6/lib
/opt/lib
也可以在/etc/ld.so.conf.d目录下创建一个.conf文件,将搜索路径一行一个的加入该文件中。以后在编译程序时,加上如下编译选项,就可以使用共享库了。
-L/usr/local/lib/apue –lapue
但需要注意的是:更改/etc/ld.so.conf的设置方法对于程序连接时的库(包括共享库和静态库)的定位已经足够了,但是对于使用了共享库的程序的执行还是不够的。这是因为为了加快程序执行时对共享库的定位速度,避免使用搜索路劲查找共享库的低效率,动态加载器是直接读取库列表文件/etc/ld.so.cache来进行搜索的。Ld.so.cache是一个非文本的数据文件,不能直接编辑,它是根据/etc/ld.so.conf中设置的路劲由/sbin/ldconfig命令将这些搜索路径下的共享库文件集中在一起而生成的(ldconfig需要root权限才能执行)。
因此,为了保证程序执行时对库的定位,在/etc/ld.so.conf中进行了库文件搜索路径设置后,还需要运行/sbin/ldconfig命令更新/etc/ld.so.cache文件。
4.2 修改环境变量LD_LIBRARY_PATH
方法一需要有root权限才能设置,而且当系统重新启动后,所有的基于GTK2的程序在运行时都将使用新安装的GTK+库。不幸的是,由于GTK+版本的改变,这有时会给应用程序带来兼容性的问题,造成程序运行不正常。为避免出现这种情况,可采用在环境变量LD_LIBRARY_PATH中指明库的搜索路径的方法,它不需要root权限,设置也简单。
设置方法如下:
$ export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH
查看LD_LIBRARY_PATH的内容的方法为:
$ echo $LD_LIBRARY_PATH
注意:LD_LIBRARY_PATH的设定是全局的,过多的使用可能会影响到其他应用程序的运行,所以多用在调试中。
4.3 链接时通过-L选型显示指定路径
在程序链接时,对于库文件(静态库和动态库)的搜索路径,可以通过-L参数显示指定库文件路径。因为-L设置的路径将被优先搜索,所以在链接的时候通常会以这种方式直接指定要链接的库的路径。如:
$ gcc main.c -o main -lfunc -L./
$ gcc test.c –o test –L./ -lapue
五、常见问题及解决方法
调用动态库的时候,有时明明已经将库的头文件在目录通过“-I”include进来了,库所在文件通过“-L”参数引导,并指定了“-l”的库名,但是通过ldd命令查看是,就是死活找不到指定链接的so库文件,这时可通过修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
makefile里面怎么正确的编译和连接生成的.so库文件,然后又是在其他程序的makefile里面如何编译和链接才能调用这个库文件和函数?
- 通过export命令设置环境变量将库的路径添加到库目录中,这种方法不太方便:
export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH:$(pwd));
- LD_LIBRARY_PATH可以在/etc/profile,~/.profile或者./bash_profile里设置,或者.bashrc里,改完后运行source /etc/profile或./etc/profile;
- 将新的路径添加进/etc/ld.so.conf,然后执行/sbin/ldconfig。