C语言的静态库和的动态库

C语言的静态库和的动态库

编写函数并编译

  1. 创建calc.c文件

    #include "calc.h"
    
    int add(int a, int b)
    {
        return a + b;
    }
    
    int sub(int a, int b)
    {
        return a - b;
    }
    
  2. 创建calc.h文件

    // 计算功能的头文件
    #ifndef __CALC_H
    #define __CALC_H
    
    int add(int, int);
    int sub(int, int);
    
    #endif
    
  3. 创建show.c文件

    #include 
    #include "show.h"
    
    void show(int l, char op, int r, int res)
    {
        printf("%d %c %d = %d\n", l, op, r, res);
    }
    
  4. 创建show.h文件

    // 输出函数的头文件
    #ifndef __SHOW_H
    #define __SHOW_H
    
    void show(int, char, int, int);
    
    #endif
    

    对代码只进行编译而不进行链接:gcc -c calc.cgcc -c show

  5. 编写接口文件math.h

    // 接口文件
    #ifndef __MATH_H
    #define __MATH_H
    
    #include "calc.h"
    #include "show.c"
    
    #endif
    

制作静态库

  • ar命令
    • ar [选项] <静态库文件> <目标文件列表>
      • 有以下几个选项
      • -r将目标插入到静态库中,已存在则更新
      • -q将目标文件追加到静态库尾
      • -d从静态库中删除目标文件
      • -t列表显示静态库中的目标文件
      • -x将静态库展开为目标文件

使用这个命令制作静态库:ar -r libmath.a calc.o show.o

使用静态库

  1. 编写主函数main.c

    #include "math.h"
    
    int main(void)
    {
        int a = 100, b = 200;
        show(a, '+', b, add(a, b));  // 使用add和show
        show(a, '-', b, sub(a, b));  // 使用sub和show
        return 0;
    }
    
  2. 编译的时候一共有三个方式(linux中指定库名的时候默认将)

    1. 直接链接静态库:gcc main.c libmath.a -o test

    2. 用l指定库名,用-L指定库路径(因为我这个是在当前路径.):gcc main.c -lmath -L. -o test

    3. 将库路径添加到LIBRARY_PATH中,然后使用-l指定库名

      export LIBRARY_PATH=$LIBRARY_PATH:/home/bhlu/study/static
      gcc main.c -lmath -o test
      
  3. 直接执行:./test

整个静态库制作和使用的顺序

  1. 编辑库的实现代码和接口文件
    • 计算模块:calc.hcalc.c
    • 显示模块:show.hshow.h
    • 接口文件:math.h
  2. 编译成目标文件
    • gcc -c calc.c
    • gcc -c show.c
  3. 打包成静态库
    • ar -r libmath.a calc.o show.o
  4. 编译
    • gcc main.c -lmath -L. -o test
  5. 使用
    • ./test

动态库的制作和使用

动态库和静态库不同,链接动态库不需要将被调用的函数代码复制包含调用代码的可执行文件中,相反链接器会在调用语句处嵌入一段指令,在该程序执行时,会加载该动态库并寻找被调用函数的入口地址并执行;动态库的扩展名是.so

编写函数并编译

动态库的函数创建和静态库一致

  1. 创建calc.c文件

    #include "calc.h"
    
    int add(int a, int b)
    {
        return a + b;
    }
    
    int sub(int a, int b)
    {
        return a - b;
    }
    
  2. 创建calc.h文件

    // 计算功能的头文件
    #ifndef __CALC_H
    #define __CALC_H
    
    int add(int, int);
    int sub(int, int);
    
    #endif
    
  3. 创建show.c文件

    #include 
    #include "show.h"
    
    void show(int l, char op, int r, int res)
    {
        printf("%d %c %d = %d\n", l, op, r, res);
    }
    
  4. 创建show.h文件

    // 输出函数的头文件
    #ifndef __SHOW_H
    #define __SHOW_H
    
    void show(int, char, int, int);
    
    #endif
    

    对代码只进行编译而不进行链接:gcc -c calc.cgcc -c show

  5. 编写接口文件math.h

    // 接口文件
    #ifndef __MATH_H
    #define __MATH_H
    
    #include "calc.h"
    #include "show.c"
    
    #endif
    

制作动态库

  • 使用这个命令制作动态库:gcc -shared -fpic cala.o show.o -o libmath.so
  • 其实也可以使用这个命令一次性完成:gcc -shared -fpic cala.c show.c -o libmath.so
    • -fPIC:大模式,生成代码比较大,运行速度比较慢,所有平台都支持
    • -fpic:小模式,生成代码比较小,运行速度比较快,仅部分平台支持

使用动态库

  1. 编写库的使用代码main.c

    #include "math.h"
    
    int main(void)
    {
        int a = 100, b = 200;
        show(a, '+', b, add(a, b));  // 使用add和show
        show(a, '-', b, sub(a, b));  // 使用sub和show
        return 0;
    }
    
  2. 编译并链接动态库(这里编译也是有三种方式,与上面静态库一致)

    1. 直接链接动态库:gcc main.c libmath.so -o test

    2. 用l指定库名,用-L指定库路径(因为我这个是在当前路径.):gcc main.c -lmath -L. -o test

    3. 将库路径添加到LIBRARY_PATH中,然后使用-l指定库名

      export LIBRARY_PATH=$LIBRARY_PATH:/home/bhlu/study/shared
      gcc main.c -lmath -o test
      
  3. 编译成功后,在执行程序之前,这里必须配置链接器的环境变量,不然会报以下错误

    # error while loading shared libraries: libmath.so: cannot open shared object file: No such file or directory
    
    # 需要配置一下链接器到的环境变量
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/bhlu/study/shared
    
  4. 执行命令./test

整个动态库制作和使用的顺序

  1. 编辑库的实现代码和接口文件
    • 计算模块:calc.hcalc.c
    • 显示模块:show.hshow.h
    • 接口文件:math.h
  2. 编译成目标文件
    • gcc -c calc.c
    • gcc -c show.c
  3. 打包成动态库
    • gcc -shared -fpic calc.o show.o -o libmath.so
  4. 编译
    • gcc main.c -lmath -L. -o test
  5. 配置链接器的环境变量
    • export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/bhlu/study/shared
  6. 使用
    • ./test

动态库的动态加载

  • 动态库(也称为共享库或动态链接库)是一种在程序运行时加载的可执行代码和数据的集合。动态加载指的是在程序运行时根据需要加载和链接动态库,而不是在编译时将其静态链接到可执行文件中。
  • 以下是一些动态加载的主要原因:
  1. 节省内存:动态库允许多个程序共享同一份代码和数据,当多个程序使用同一个动态库时,只需在内存中加载一次即可。
  2. 灵活性:动态加载允许程序在运行时根据需要加载额外的功能,在不停止程序的情况下可以添加和移除一些功能。
  3. 易于更新和维护:修复漏洞不需要重新编译而去调整整个程序。
  4. 增加代码的复用性:当多个程序使用同一个库的函数时,这样可以减少代码的冗余。

在使用动态库的动态加载时,为了避免后面这种情况:假设一个程序执行了10分钟,而它用到动态库的地方就只需1分钟,为了实现动态库使用完就立即回收,从而减少内存的占用,我们采用了下面的这个操作方法。

  • load.c(里面用的到的函数在代码后面会进行解释)

    #include 
    #include 
    
    int main(void)
    {
        // 1. 将动态库载入内存-dlopen函数
        void *handle = dlopen("/home/bhlu/study/shared/libmath.so", RTLD_NOW);
        if(handle == NULL)
        {
            // 当载入不成功的时候
            fprintf(stderr, "dlopen: %s\n", dlerror());
            return -1;
        }
        
        // 2. 获取库中函数的地址-dlsym函数
        int (*add)(int, int) = dlsym(handle, "add");
        if(add == NULL)
        {
            fprintf(stderr, "dlsym: %s\n", dlerror());
            return -1;
        }
        int (*sub)(int, int) = dlsym(handle, "sub");
        if(sub == NULL)
        {
            fprintf(stderr, "dlsym: %s\n", dlerror());
            return -1;
        }
        int (*show)(int, char, int, int) = dlsym(handle, "show");
        if(show == NULL)
        {
            fprintf(stderr, "dlsym: %s\n", dlerror());
            return -1;
        }
        
        // 3. 使用
        int a = 100, b = 200;
        show(a, '+', b, add(a, b));
        show(a, '-', b, sub(a, b));
        
        // 4. 卸载动态库
        // dlclose(handle)
        if(dlclose(handle))
        {
            // 判断是否卸载成功,其实当有其他程序再调用这个动态库的时候,是卸载不了的
            fprintf(stderr, "dlclose: %s\n", dlerror());
            return -1;
        }
        
        return 0;
    }
    
  • 使用相关命令编译load.cgcc load.c -ldl -o load,因为这里用到了libld这个库,所以编译的时候需要指定一下,不过我在我的ubuntu环境下不加这个也可以,等之后有时间了解一下原因。


相关函数的介绍

  • 使用动态加载的函数,需要加载dlfcn.h这个头文件:#include
  • dlopen函数
    • void *dlopen(char const *filename, int flag);
      • 功能:将动态库载人内存并获得其访问句柄
      • 参数
        • filename:动态库路径,如果只给文件名,会根据LD_LIBRARY_PATH环境变量去搜索,上面示例给的是绝对路径
        • flag:加载方式
          • RTLD_LAZY:延时加载,使用动态库中的符号时才会真的加载进内存
          • RTLD_NOW:立即加载
      • 返回值:成功就返回动态库的访问句柄,用于后续函数的调用,失败就返回NULL
  • dlsym函数
    • void *dlsym(void *handle, char const *symbol);
      • 功能:从已加载的动态库中获取指定名称的函数地址
      • 参数
        • handle:动态库访问句柄,即dlopen函数的返回值
        • symbol:符号名,即函数名
      • 返回值:成功返回函数的地址,失败返回NULL(因为函数返回值是void*类型,所以定义返回值的时候,需要造型跟实际函数类型一致的指针,才能使用
  • dlclose函数
    • int dlclose(void *handle);
      • 功能:从内存中卸载动态库
      • 参数:handle动态库访问句柄
      • 返回值:成功返回0,失败返回非0(只有没有程序使用这个动态库,才能真正的被卸载
  • dlerror函数
    • char *dlerror(void);
      • 功能:获取加载、使用和卸载动态库过程中所发生的错误
      • 返回值:有错误则返回指向错误信息字符串的指针,否则返回NULL

上面使用到的fprintf函数,因为这里想使用的是错误输出,所以使用的标准错误流stderr,与标准输出流 stdout 不同,标准错误流用于输出错误和警告信息。

动态加载的辅助工具

  1. 查看符号表:nm

    • 列出目标文件、可执行程序、静态库、动态库中的函数

      nm libmath.a 
      
      # 输出
      calc.o:
      0000000000000000 T add
      0000000000000018 T sub
      
      show.o:
                       U printf
      0000000000000000 T show
      
  2. 查看依赖:ldd

    • 查看可执行文件或者共享库所依赖的共享库

      ldd test
      
      # 输出
              linux-vdso.so.1 (0x00007fff3b0f5000)
              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5d1da00000)
              /lib64/ld-linux-x86-64.so.2 (0x00007f5d1dd50000)
      

你可能感兴趣的:(unix_c,c语言,开发语言)