Linux的环境编程之静态库和动态库

静态库和动态库的链接各有优缺点,静态库会在文件编译二进制文件的时候,会直接链接到二进制文件里面,这样子文件体积比较大,而且如果我们更改分享库的时候需要重新编译.动态库只是保存分享库和二进制文件的依赖关系.在运行阶段才会加载动态库

参考书籍:<Linux的环境编程,从应用到内核>

操作系统:deepin

编译器:gcc

简单动态库与静态库分析

这里要区分一下静态链接和动态链接的区别以及,静态库和动态库的区别.

静态库

静态库在linux下是以”.a”结尾的,静态库将汇编 生成的.o文件进打包成一个.a文件,静态库可以在可执行文件编译的时候直接链接进可执行文件里面,移植方便,在新的操作系统上面运行不用搭建库环境.

  1. 创建静态库

    1
    2
    3
    gcc  -c test1.c test2.c test3.c   ;-c命令是只编译不链接,会自动生成test1.o test2.o test3.o
    ar -crv libtest.a test1.o test2.o test3.o ;使用ar工匠打包成静态库
    上面我们需要注意一下静态库和动态库的命名上市 lib****.so lib****.a ;我们在链接库的时候有时候会指定特定的库时,-l*** 就行,然后系统就自动会解析出来 -l lib***.so ,这个也是为什么这样命名

动态库

动态库是在可执行文件编译的时候将库函数链接加载进去,在可执行文件 运行时依靠这依赖关系加载.这样可以使代码轻便,而且更新库文件的话,可执行文件不要重新编译.

  1. 制作动态库

    1
    gcc -Wall -shared test1.c  -o libtest.so

简单小例子

  1. 编译一个简单的C文件

  2. test.c
    1
    vim test1.c
  3. 1
    2
    3
    4
    5
    6
    7
    8
    内容如下:
    #include "stdio.h"
    #include "stdlib.h"
    int main(void) {

    printf("Welcome !!\r\n");
    return 0;
    }
  4. 1
    gcc test1.c -o test1  ;这是默认动态链接
  5. 1
    gcc test1.c -o test1_1 -static ;这是静态链接

  1. 对比上图可以发现静态链接的可执行文件比动态链接的大特别多.

  2. 使用ldd(查询动态依赖关系)命令查看

    可以看出来使用动态库链接的时候 使用ldd命令看到依赖的库,但是静态链接的会提示不是一个动态可执行文件,因为他是动态依赖的,运行过程不要动态依赖,

编译

生成和使用动态库

编译生成一个动态库

  1. 1
    2
    3
    4
    5
    6
    7
    #include "stdio.h"
    #include "stdlib.h"
    void Funful_call(void) {

    printf("Welcome \r\n");
    printf("调用动态库\r\n");
    }
  2. 1
    gcc -Wall -shared lib_funful.c -o  libfunful.so

使用动态库

  1. 创建一个test3.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include "stdio.h"
    #include "stdlib.h"
    int main(void) {

    Funful_call();


    return 0;
    }
  2. 编译

    1
    gcc -W test3.c  -o test3 -L ./  -lfunful
  3. 运行会发现找不到libfunful.so ,这是因为我们上面只是指定了编译时候的动态库地址,但是我们运行的时候还是在系统默认的库地址(/usr/lib ;/lib)进行链接;可以通过/ect/ld.so.conf,配置文件和环境变量LD_LiBRARY_PATH指示额外的动态库路径.

    还可以通过libfunful.so移到/usr/lib或者/lib里面,这样我们的库就可以正常运行.

手动加载动态库

系统的C库提供了一系列接口,开手动加载动态库功能.

头文件: #include “dlfcn.h” ;//可是使用find命令查找

头文件: /bits/dlfcn.h 里面是对相应的宏定义

相关函数介绍

dlopen

  1. 打开接口函数,返回一个句柄,然后通过dlsym得到相应的值

    1
    void *dlopen (const char *file, int mode) __THROWNL;
  2. 参数:

    1. const char *file;找到文件地址指针

    2. int mode; 打开的模式,在linux按照功能可以分为三类

      1. 解析方式:

        ​ RTLD_LAZY 在dlopen返回前,对于动态库中的未定义的符号不执行解析

        ​ RTLD_NOW 在dlopen返回前,解析出所有未定义符号,如果解析不出来,在dlopen会返回NULL

      2. 作用范围:

        ​ RTLD_GLOBAL 动态库中定义的符号可被其后打开的其它库解析

        ​ RTLD_LOCAL 与RTLD_GLOBAL作用相反,动态库中定义的符号不能被其后打开的其它库重定位。

      3. 作用方式

        ​ RTLD_NODELETE 在dlclose()期间不卸载库,并且在以后使用dlopen()重新加载库时不初始化库中的静态变量

        ​ RTLD_NOLOAD 不加载库

        ​ RTLD_DEEPBIND 在搜索全局符号前先搜索库内的符号,避免同名符号的冲突。

    dlclose

  3. 关闭一个分享库,这个句柄在调用dlclose之后就不能在使用.

    1
    1.  int dlclose (void *handle) THROWNL __nonnull ((1));
  4. 参数:

    这个形参是dlopen返回的句柄.

dlsym

  1. 这个函数是根据链接库的句柄和符号,返回相应的符号地址.

    1
    2
    void *dlsym (void *__restrict __handle,
    const char *__restrict __name) __THROW __nonnull ((2));
  2. 参数:

    1. void *restrict handle; 这个是链接库句柄
    2. const char *restrict name ;符号的字符串指针

手动加载动态库实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "stdio.h"
#include "stdlib.h"
#include "dlfcn.h"

int main(void) {

void *dlib=dlopen("./libfunful.so",RTLD_NOW);

if(dlib == NULL ) {
printf("open libfunful.so failed\r\n");
return -1;
}
void (*dfunc)(void) =dlsym(dlib,"Funful_call");
if(dfunc==NULL) {
printf("dlsym failed \r\n");
return -1;
}
dfunc();
dlclose(dlib);
return 0;
}

编译

1
gcc test3.c -ldl -o test3   ;这里需要我们指定一下依赖的dl库,否则会提示undefined dl的函数

总结

上面讲解的动态库加载分为两中方法,一种是更新环境变量的方法,一种是手动加载各有利弊,

-------------本文结束感谢您的阅读-------------