Linux下静态库和动态库

来源:互联网 发布:3d样板房软件 编辑:程序博客网 时间:2024/05/16 12:04
一、基本概念
1.1、什么是库
   在windows平台和linux平台下都大量存在着库。
   本质上来说库是一种可执行的二进制代码(但不可以独立执行),可以被操作系统载入内存执行。
   由于windows和linux的平台不同(主要是编译器、汇编器和连接器的不同),因此二者库的二进制是不兼容的。
   本文仅限于介绍linux下的库。
1.2、库的种类
   linux下的库有两种:静态库和共享库(动态库)。
   二者的不同点在于代码被载入的时刻不同:
   静态库的代码在编译过程中已经被载入可执行程序,因此生成的可执行程序体积较大。静态库用.a为后缀,例如:libhello.a
   共享库(动态库)的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此生成的可执行程序代码体积较小。
   动态库通常用.so为后缀,例如:libhello.so
   共享库(动态库)的好处是:不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
   为了在统一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如:libhello.so.1.0,由于程序联接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。
   ln -s libhello.so.1.0 libhello.so.1
   ln -s libhello.so.1 libhello.so
1.3、静态库,动态库文件在linux下是如何生成的:
   以下面的代码为例,生成上面用到的hello库:
   /*hello.c*/
   #include "hello.h"
   void sayhello()
   {
       printf("hello,world");
   }
   首先用gcc编译该文件,在编译时可以使用任何合法的参数编译,例如-g加入条是代码等:
   gcc -c hello.c -o hello.o (-g 为了调试用,加个-g是为了gdb用,-o output_filename 确定输出文件名称,-c只编译不联接)
   1、成成静态库  生成静态库使用ar工具,其实ar是archive的意思,静态库生成必须经过gcc -c 到ar cqs阶段,即必须生成.o文件再生成.a文件(仅个人理解,无法验证)
      ar  cqs libhello.a hello.o
   2、生成动态库,用gcc来完成,由于可能存在多个版本,因此通常制定版本号:
      gcc -shared -o libhello.so.1.0 hello.o
1.4、库文件是如何命名的,有没有什么规范:
   在linux下,库文件一般放在/usr/lib和/lib下,静态库的名字一般为libxxxxx.a,其中xxxxx是该lib的名称;动态库的名字一般为:libxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号。
1.5、可执行程序在执行的时候如何定位共享库(动态库)文件:
   当系统加载可执行代码(即库文件)的时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径,此时就需要系统动态载入器(dynameic linker/loader)
   对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的DT_RPATH段->环境变量LD_LIBRARY_PATH-->/etc/ld.so.cache文件列表-->/lib/,/usr/lib目录找到库我呢鉴后将其载入内存。
   如:export LD_LIBRARY_PATH=‘'pwd' 将当前文件目录添加为共享目录。
1.6、使用ldd工具,查看可执行程序依赖哪些动态库或者动态库依赖哪些动态库:
   ldd命令可以查看一个可执行程序依赖的共享库
   例如:ldd /bin/lnlibc.so.6
         =>/lib/libc.so.6(0X40021000)/lib/ld-linux.so.2
         =>/lib/ld-linux.so.2(0X40000000)
   可以看到ln命令依赖于libc库和ld-linux库
1.7、使用nm工具,查看静态库和动态库中有哪些函数名:
   (T类表示函数是当前库中定义的,U类表示函数是被调用的,在其他库中定义的,W类是当前库中定义,被其他库中的函数覆盖):    有时候可能需要查看一个库中到底有哪些函数,nm工具可以打印出库中的设计到的所有符号,这里的库既可以是静态的也可是是动态的。nm列出的符号有很多,常见的有三那种:
    T类:是在库中定义的函数,用T表示,这是最常见的;
    U类:是在库中被调用,但并没有在库中定义(表明需要其他库的支持),用U表示;
    W类:是所谓的“”弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
    例如,假设开发者希望知道上文提到的hello库中是否引用了printf():
        nm libhello.so|grep printf
    发现printf是U类符号,说明printf被引用,但是并没有在库中定义。由此可以推断,要正常使用hello库,必须有其他库的支持,使用ldd工具查看hello依赖哪些库:
        ldd libhello.so
        lib.so.6-->/lib/libc.so.6(0X400la00)
        /lib/ld-linux.so.2-->/lib/ld-linux.so.2(0X400000)
     从上面的结果可以继续查看printf最终在那里被定义,有兴趣可以go on
1.8、使用ar工具,可以生成静态库,同时可以查看静态库中包含哪些.o文件,即有哪些源文件构成。
    可以使用ar -t libname.a 来查看一个静态库由哪些.o文件构成。
    可以使用ar q libname.a xxx1.o  xxx2.o  xxx3.o 生成静态库
1.9、 如何查看静态库和动态库是32位还是64位下的库:
    如果是动态库,可以使用file *.so;
    如果是静态库,可以使用objdump -x *.a

二 Linux下进行程序设计时,关于库的使用:
2.1 gcc/g++命令中关于库的参数:
    -shared :该选项指定生成动态链接库;
    -fPIC :表示编译为未知独立的代码,不用此选项的话,编译后的代码是位置相关的,所以动态载入时,是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
    -L :指定链接库的路径,-L. 表示要链接的库在当前目录中
    -ltest:指定链接库的名字为test,编译其查找动态链接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
    LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
    当然如果有root权限的花,可以修改/etc/ld.so.conf文件,然后调用/sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用修改LD_LIBRARY_PATH环境变量的方法了。

2.2 调用动态库的时候,有几个问题会经常碰到:
    有时,明明已经将库的头文件所在目录通过-I include进来了,库所在文件通过-L参数引导,并指定了-l的库名,但通过ld命令查看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

2.3 静态库链接时搜索路径的顺序:
    1.ld会去gcc/g++命令中的参数-L;
    2.在找gcc的环境变量LIBRARY_PATH,它指定程序静态链接库文件搜索路径;
        export LIBRARY_PATH=$LIBRARY_PATH:data/home/XXXX
    3.在找默认库目录/lib /usr/lib  /usr/local/lib,这是当初compile gcc时写在 程序内的。
2.4 动态链接时、执行时搜索路径顺序:
    1. 编译目标代码时指定的动态库搜索路径;
    2. 环境变量LD_LIBRARY_PATH指定动态库搜索路径,它指定程序动态链接库文件搜索路径;
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:data/home/billchen/lib
    3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
    4. 默认的动态库搜索路径/lib;
    5. 默认的动态库搜索路径/usr/lib。

2.5、静态库和动态库同时存在时,gcc/g++默认链接的时动态库:
    当一个库同时存在静态库和动态库时,比如libmysqlclient.a和libmysqlclient.so同时存在时,在Linux下,动态库和静态库同时存在时,gcc/g++的链接程序,默认链接的动态库。

2.6、有关环境变量:
    LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
    LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径

2.7、库的依赖问题:
   比如我们有一个基础库libbase.a,还有一个依赖libbase.a编译的库,叫做libchild.a;在我们编译程序时,一定要先-lchild再-lbase。 如果使用 -lbase -lchild,在编译时将出现一些函数undefined,而这些函数实际上已经在base中已经定义;
   为什么会有库的依赖问题?
   1、静态库解析符号引用:
   链接器ld是如何使用静态库来解析引用的。在符号解析阶段,链接器从左至右,依次扫描可重定位目标文件(*.o)和静态库(*.a)。在这个过程中,链接器将维持三个集合:
   集合E:可重定位目标文件(*.o文件)的集合。
   集合U:未解析(未定义)的符号集,即符号表中UNDEF的符号。
   集合D: 已定义的符号集。
   初始情况下,E、U、D均为空。
   1)、对于每个输入文件f,如果是目标文件(.o),则将f加入E,并用f中的符号表修改U、D(在文件f中定义实现的符号是D,在f中引用的符号是U),然后继续下个文件。
   2)、如果f是一个静态库(.a),那么链接器将尝试匹配U中未解析符号与静态库成员(静态库的成员就是.o文件)定义的符号。如果静态库中某个成员m(某个.o文件)定义了一个符号来解析U中引用,那么将m加入E中,同时使用m的符号表,来更新U、D。对静态库中所有成员目标文件反复进行该过程,直至U和D不再发生变化。此时,静态库f中任何不包含在E中的成员目标文件都将丢弃,链接器将继续下一个文件。
   3)、当所有输入文件完成后,如果U非空,链接器则会报错,否则合并和重定位E中目标文件,构建出可执行文件。 到这里,为什么会有库的依赖问题已经得到解答: 因为libchild.a依赖于libbase.a,但是libbase.a在libchild.a的左边,导致libbase.a中的目标文件(*.o)根本就没有被加载到E中,所以解决方法就是交换两者的顺序。当然也可以使用-lbase -lchild -lbase的方法。
   参考文章:http://pananq.com/index.php/page/3/

2.8、动态库升级问题:
   在动态链接库升级时,不能使用cp newlib.so oldlib.so,这样有可能会使程序core掉;而应该使用:
   rm oldlib.so 然后 cp newlib.so oldlib.so
   或者
   mv oldlib.so oldlib.so_bak
   cp newlib.so oldlib.so


0 0