静态库、共享库和动态加载库

在Linux C/C++中,使用库的方式有3中:
1.静态库
1).创建object文件: 
    gcc -c lib_c.c -o mylib.o
   (the -c option says not to run the linker. )
    输出为lib_h.o

2).创建后缀为.a的静态库:
    ar rcs mylib.a mylib.o
    其中ar(archive)中的rcs的意思是:r表明将模块加入到静态库中,c表示创建静态库,s表示生产索引。
    输出为mylib.a
    (使用objdump -D mylib.o和objdump -D mylib.a后使用beyondCompare比较之后,发现.o与.a文件内容是一样的)

3).使用mylib.a:   
    gcc -static use_lib.c  -L. mylib.a -o testLib
    (注意1:参数-static表示使用静态库方式链接,如果没有-staic将使用共享库方式链接。-L.表示将当前目录加入到库搜索路径。)
    (注意2:使用ldd testLib,如果是静态库方式链接,输出为:not a dynamic executable;如果是共享库则不然。通过比较,我们会发现以静态库方式编译输入文件会大很多)
    
4). 查看静态库符号:
     nm mylib.a 
     输出为:
mylib.o:
U puts
0000000000000000 T testLib
U表示在库中被调用,但并没有在库中定义(表明需要其他库支持),T表示是库中定义的函数。


测试代码:

lib_h.h

void testLib();


lib_c.c

#include 
#include "lib_h.h"
void testLib()
{
  printf("hello, I'm lib, welcome to use me \n");
}


use_lib.c
#include "lib_h.h"
int main()
{
  testLib();
}

2.共享库 
1).创建object文件: 
    gcc -c -fPIC mySharedLib.c -o mySharedLib.o
    (-fpic或者-fPIC表明创建position independent code,这是创建共享库必须的。)
    输出为mySharedLib.o

2).创建后缀为.so的共享库:
    gcc -shared -o libmySharedLib.so mySharedLib.o  
    (-shared表明创建一个能被其它目标文件链接的共享库)
    输出为libmySharedLib.so
    (这时会发现mySharedLib.so比mySharedLib.o大许多,使用nm查看,发现mySharedLib.so比mySharedLib.o多出许多符号。

3).使用libmySharedLib.so
    gcc -o testSharedLib testSharedLib.c -lmySharedLib -L.
   (注意:lmySharedLib来自于libmySharedLib.so,所以在上一步生成.so文件时,必须带前缀lib,否则这里找不到lmySharedLib。
           -L.表示当前相对路径, -L后面也可以跟绝对路径 -L/home/xxx/Library/Shared )
   
4).执行./testSharedLib
    ./testSharedLib: error while loading shared libraries: libmySharedLib.so: cannot open shared object file: No such file or directory。 
    也可以通过ldd libmySharedLib输出libmySharedLib.so => not found(找不到共享库libmySharedLib.so)
    怎么办?

    原因是共享库不在系统默认的路径里面,在shell执行export LD_LIBRARY_PATH=./


测试代码:
mySharedLib.h
double mean(double, double);

mySharedLib.c

double mean(double a, double b)
{
        return (a+b) / 2;
}

testSharedLib.c

#include 
#include "mySharedLib.h"

int main(int argc, char* argv[])
{
        double v1, v2, m;
        v1 = 5.2;
        v2 = 7.9;
        m  = mean(v1, v2);
        printf("The mean of %3.2f and %3.2f is %3.2f\n", v1, v2, m);
        return 1;
}

3.动态加载库
    动态加载库(dynamically loaded (DL) libraries)是指在程序运行过程中可以加载的函数库。而不是像共享库一样在程序启动的时候加载。在Linux中,动态库的文件格式跟共享库没有区别,主要区别在于共享库是运行时加载。(DL对于实现插件和模块非常有用,因为他们可以让程序在允许时等待插件的加载。)


1).创建动态库文件(与共享库一样)   
    gcc -c -fPIC mySharedLib.c -o mySharedLib.o
    gcc -shared -o libmySharedLib.so mySharedLib.o
2).加载动态库
    gcc -o testDll testDll.c -ldl -Wall

3).动态加载库函数介绍:
    (1) dlopen
函数原型:void *dlopen(const char *libname,int flag);
功能描述:表示将库装载到内存。(dlopen必须在dlerror,dlsym和dlclose之前调用,)
如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。
参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:
a.根据环境变量 LD_LIBRARY_PATH 查找
b.根据/etc/ld.so.cache查找
c.查找依次在/lib和/usr/lib目录查找。
flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内 存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。
    (2) dlerror
函数原型:char *dlerror(void);
功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。
    (3) dlsym
函数原型:void *dlsym(void *handle,const char *symbol);
功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。
如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,
    (4) dlclose
函数原型:int dlclose(void *);
功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。

测试代码:

mySharedLib.h

double mean(double, double);
void helloDll();

mySharedLib.c

#include 
double mean(double a, double b)
{
    double x = (a+b) / 2;
        printf("Hello, mean = %f \n", x);
        return x;
}

void helloDll()
{
  printf("Hello, Dll ! \n");
}

testDll.c

#include 
#include  /* 必须加这个头文件 */
 
int main()
{
   void *lib_handle;
   void (*fn1)(void);
   void (*fn2)(double, double);
   char *error;
 
   lib_handle = dlopen("libmySharedLibx.so", RTLD_LAZY);
   if (!lib_handle) 
   {
      fprintf(stderr, "%sn \n", dlerror());
      return 1;
   }
 
   fn1 = dlsym(lib_handle, "helloDll");
   if ((error = dlerror()) != NULL)  
   {
      fprintf(stderr, "%sn", error);
      return 1;
   } 
   fn1();

   fn2 = dlsym(lib_handle, "mean");
   if ((error = dlerror()) != NULL)  
   {
      fprintf(stderr, "%sn \n", error);
      return 1;
   } 
   fn2(5.5, 6.6);
   
   dlclose(lib_handle);
 
   return 0;
}

总结:
    1.静态库的链接应用是在编译时期完成的,程序在运行时与静态库再无瓜葛,移植方便。
    2.静态库使用浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。  3.如果静态库更新了,所以使用它的应用程序都需要重新编译、发布。(对于客户端来说,可能一个很小的改动,却导致整个程序重新下载,全量更新)。共享库将一些程序升级变得简单。
    4.共享库把对一些库函数的链接载入推迟到程序运行的时期。
    5.共享库实现了进程之间的资源共享,节省了空间。

    6.共享库可以真正做到链接载入完全由程序员在程序代码中控制(动态加载)。


你可能感兴趣的:(系统(Linux))