• 静态库和动态库


    库的概述
     
      在windows和Linux下都存在着大量的库,库在本质上来说,是一种可执行代码的二进制形式,可以被操作系统载入内存执行
    我们通常将一些通用函数写成函数库,所以库是别人写好的,现有的,成熟的,可以复用的代码,你可以使用但要必须得遵守许
    可协议。在我们现实开发过程中,不可能每一份代码都从头编写,当我们拥有库时,我们就可以直接将我们所需要的文件链接到
    我们的程序中。程序中用到的数学函数,其二进制文件已经生成在库里,最后一步的链接的过程就涉及对数学库中函数的链接。
    这样可以为我们节省大量的时间,提高开发效率。Linux下库分为两种,静态库和动态库。这两种库相同点是两种库都是由.o文件
    生成的。
     
    库的特点
     
      库是在链接阶段和相应的.o目标文件形成可执行文件,根据链接方式的不同。可以将库分为静态库和动态库。
      当使用静态库时,连接器会找出程序所需的函数,然后将它们复制到执行文件,由于这种复制是完整的,所以一旦链接成功,
    静态库在不存在的情况下可执行文件能够正常执行。然而动态库与静态库截然不同,动态库会在执行程序内留下一个标记指明当
    程序执行时必须载入的库文件,所以当执行文件执行时才动态加载库文件,而使用动态库必然节省空间。
     
    静态库和动态库
     
      
    1, 静态库
      静态库文件名的命名方式是“libxxx.a”,库名前加”lib”,windows和linux下都是后缀用”.a”,“xxx”为静态库名,
    windows下的静态库名也叫libxxx.a;
    链接时间: 静态库的代码是在编译过程中被载入程序中。
    链接方式:静态库的链接是将整个函数库的所有数据都整合进了目标代码。这样做优点是在编译后的执行程序不在需要外
    部的函数库支持,因为所使用的函数都已经被编进去了。缺点是,如果所使用的静态库发生更新改变,你的程序必须重新
    编译。
     
    2, 动态库
      动态库的命名方式与静态库类似,前缀相同为“lib”,linux下后缀名为“.so(shared object)”即libxxx.so;而windows
    下后缀名为“.dll(dynamic link library)”即libxxx.dll;
    链接时间:动态库在编译的时候并没有被编译进目标代码,而是当你的程序执行到相关函数时才调用该函数库里的相应函
    数。这样做缺点是因为函数库并没有整合进程序,所以程序的运行环境必须提供相应的库。优点是动态库的改变并不影响
    你的程序,所以动态函数库升级比较方便
     
    注意:
      当同一个程序分别使用静态库,动态库两种方式生成两个可执行文件时,静态链接所生成的文件所占用的内存要远远
    大于动态链接所生成的文件。原因是静态链接是在编译时将所有的函数都编译进了程序,而动态链接是在程序运行时由
    操作系统帮忙把动态库调入到内存空间中使用。另外如果动态库和静态库同时存在时,链接器优先使用动态库。
     
    静态库的创建
     
    1.静态库

    在 Linux 中静态库由程序 ar 生成,现在静态库已经不像之前那么普遍了,这主要是由于程序都在使用动态库。关于静态库的命名规则如下:

      1)在 Linux 中静态库以 lib 作为前缀,以.a 作为后缀,中间是库的名字自己指定即可,即: libxxx.a
      2)在 Windows 中静态库一般以 lib 作为前缀,以 lib 作为后缀,中间是库的名字需要自己指定,即: libxxx.lib

    2. 生成静态链接库
    生成静态库,需要先对源文件进行汇编操作 (使用参数 -c) 得到二进制格式的目标文件 (.o 格式), 然后在通过 ar 工具将目标文件打包就可以得到静态库文件了 (libxxx.a)。

    使用 ar 工具创建静态库的时候需要三个参数:

      1)参数c:创建一个库,不管库是否存在,都将创建。
      2)参数s:创建目标文件索引,这在创建较大的库时能加快时间。
      3)参数r:在库中插入模块 (替换)。默认新的成员添加在库的结尾处,如果模块名已经在库中存在,则替换同名的模块。

    3.生成静态链接库的具体步骤:

    1)需要将源文件进行汇编,得到 .o 文件,需要使用参数 -c

    # 执行如下操作, 默认生成二进制的 .o 文件
    # -c 参数位置没有要求
    $ gcc 源文件(*.c) -c

    2)将得到的 .o 进行打包,得到静态库

    $ ar rcs 静态库的名字(libxxx.a) 原材料(*.o)

    3)发布静态库

    # 发布静态库
    1. 提供头文件 **.h
    2. 提供制作出来的静态库 libxxx.a

    静态库制作举例

    1. 准备测试程序

    在某个目录中有如下的源文件,用来实现一个简单的计算器

    # 目录结构 add.c div.c mult.c sub.c -> 算法的源文件, 函数声明在头文件 head.h
    # main.c中是对接口的测试程序, 制作库的时候不需要将 main.c 算进去
    .
    ├── add.c
    ├── div.c
    ├── include
    │   └── head.h
    ├── main.c
    ├── mult.c
    └── sub.c

    加法计算源文件 add.c:

     
    C代码:

    #include <stdio.h>
    #include "head.h"

    int add(int a, int b)
    {
    return a+b;
    }


    减法计算源文件 sub.c:

     
    C代码:

    #include <stdio.h>
    #include "head.h"

    int subtract(int a, int b)
    {
    return a-b;
    }


    乘法计算源文件 mult.c:

     
    C代码:

    #include <stdio.h>
    #include "head.h"

    int multiply(int a, int b)
    {
    return a*b;
    }

    减法计算的源文件 div.c

     
    C代码:

    #include <stdio.h>
    #include "head.h"

    double divide(int a, int b)
    {
    return (double)a/b;
    }

    头文件 head.h

     
    C代码:

    #ifndef _HEAD_H
    #define _HEAD_H
    // 加法
    int add(int a, int b);
    // 减法
    int subtract(int a, int b);
    // 乘法
    int multiply(int a, int b);
    // 除法
    double divide(int a, int b);
    #endif

    测试文件 main.c

     
    C代码:

    #include <stdio.h>
    #include "head.h"

    int main()
    {
    int a = 20;
    int b = 12;
    printf("a = %d, b = %d\n", a, b);
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", subtract(a, b));
    printf("a * b = %d\n", multiply(a, b));
    printf("a / b = %f\n", divide(a, b));
    return 0;
    }


    2.生成静态库

    第一步:将源文件 add.c, div.c, mult.c, sub.c 进行汇编,得到二进制目标文件 add.c, div.c, mult.c, sub.o

    # 1. 生成.o
    $ gcc add.c div.c mult.c sub.c -c
    sub.c:2:18: fatal error: head.h: No such file or directory
    compilation terminated.

    # 提示头文件找不到, 添加参数 -I 重新头文件路径即可
    $ gcc add.c div.c mult.c sub.c -c -I ./include/

    # 查看目标文件是否已经生成
    $ tree
    .
    ├── add.c
    ├── add.o # 目标文件
    ├── div.c
    ├── div.o # 目标文件
    ├── include
    │   └── head.h
    ├── main.c
    ├── mult.c
    ├── mult.o # 目标文件
    ├── sub.c
    └── sub.o # 目标文件

    第二步:将生成的目标文件通过 ar 工具打包生成静态库

    # 2. 将生成的目标文件 .o 打包成静态库
    $ ar rcs libcalc.a a.o b.o c.o # a.o b.o c.o在同一个目录中可以写成 *.o

    # 查看目录中的文件
    $ tree
    .
    ├── add.c
    ├── add.o
    ├── div.c
    ├── div.o
    ├── include
    │   └── `head.h ===> 和静态库一并发布
    ├── `libcalc.a ===> 生成的静态库
    ├── main.c
    ├── mult.c
    ├── mult.o
    ├── sub.c
    └── sub.o

    第三步:将生成的的静态库 libcalc.a 和库对应的头文件 head.h 一并发布给使用者就可以了。

    # 3. 发布静态库
    1. head.h => 函数声明
    2. libcalc.a => 函数定义(二进制格式)

    3.静态库的使用

    当我们得到了一个可用的静态库之后,需要将其放到一个目录中,然后根据得到的头文件编写测试代码,对静态库中的函数进行调用。

    # 1. 首先拿到了发布的静态库
    `head.h` 和 `libcalc.a`

    # 2. 将静态库, 头文件, 测试程序放到一个目录中准备进行测试
    .
    ├── head.h # 函数声明
    ├── libcalc.a # 函数定义(二进制格式)
    └── main.c # 函数测试

    编译测试程序,得到可执行文件。

     

    # 3. 编译测试程序 main.c
    $ gcc main.c -o app
    /tmp/ccR7Fk49.o: In function `main':
    main.c:(.text+0x38): undefined reference to `add'
    main.c:(.text+0x58): undefined reference to `subtract'
    main.c:(.text+0x78): undefined reference to `multiply'
    main.c:(.text+0x98): undefined reference to `divide'
    collect2: error: ld returned 1 exit status

    上述错误分析:

    编译的源文件中包含了头文件 head.h, 这个头文件中声明的函数对应的定义(也就是函数体实现)在静态库中,程序在编译的时候没有找到函数实现,因此提示 undefined reference to xxxx。

    解决方案:在编译的时将静态库的路径和名字都指定出来

    -L: 指定库所在的目录 (相对或者绝对路径)
    -l: 指定库的名字,需要掐头 (lib) 去尾 (.a) 剩下的才是需要的静态库的名字

    # 4. 编译的时候指定库信息
    -L: 指定库所在的目录(相对或者绝对路径)
    -l: 指定库的名字, 掐头(lib)去尾(.a) ==> calc
    # -L -l, 参数和参数值之间可以有空格, 也可以没有 -L./ -lcalc
    $ gcc main.c -o app -L ./ -l calc

    # 查看目录信息, 发现可执行程序已经生成了
    $ tree
    .
    ├── app # 生成的可执行程序
    ├── head.h
    ├── libcalc.a
    └── main.c

     动态库

    1.关于动态库的命名规则如下:

    在 Linux 中动态库以 lib 作为前缀,以.so 作为后缀,中间是库的名字自己指定即可,即: libxxx.so
    在 Windows 中动态库一般以 lib 作为前缀,以 dll 作为后缀,中间是库的名字需要自己指定,即: libxxx.dll

    2. 生成动态链接库
    生成动态链接库是直接使用 gcc 命令并且需要添加 -fPIC(-fpic) 以及 -shared 参数。

    -fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
    -shared参数的作用是告诉编译器生成一个动态链接库。

    生成动态链接库的具体步骤:

    1)将源文件进行汇编操作,需要使用参数 -c, 还需要添加额外参数 -fpic /-fPIC

    # 得到若干个 .o文件
    $ gcc 源文件(*.c) -c -fpic

    2)将得到的.o 文件打包成动态库,还是使用 gcc, 使用参数 -shared 指定生成动态库 (位置没有要求)

    $ gcc -shared 与位置无关的目标文件(*.o) -o 动态库(libxxx.so)

    3)发布动态库和头文件

    # 发布
    1. 提供头文件: xxx.h
    2. 提供动态库: libxxx.so

    动态库制作举例

    在此还是以上面制作静态库使用的实例代码为例来制作动态库,代码目录如下:

    # 举例, 示例目录如下:
    # 目录结构 add.c div.c mult.c sub.c -> 算法的源文件, 函数声明在头文件 head.h
    # main.c中是对接口的测试程序, 制作库的时候不需要将 main.c 算进去
    .
    ├── add.c
    ├── div.c
    ├── include
    │   └── head.h
    ├── main.c
    ├── mult.c
    └── sub.c

    第一步:使用 gcc 将源文件进行汇编 (参数-c), 生成与位置无关的目标文件,需要使用参数 -fpic或者-fPIC



    # 1. 将.c汇编得到.o, 需要额外的参数 -fpic/-fPIC
    $ gcc add.c div.c mult.c sub.c -c -fpic -I ./include/

    # 查看目录文件信息, 检查是否生成了目标文件
    $ tree
    .
    ├── add.c
    ├── add.o # 生成的目标文件
    ├── div.c
    ├── div.o # 生成的目标文件
    ├── include
    │   └── head.h
    ├── main.c
    ├── mult.c
    ├── mult.o # 生成的目标文件
    ├── sub.c
    └── sub.o # 生成的目标文件


    第二步:使用 gcc 将得到的目标文件打包生成动态库,需要使用参数 -shared

    # 2. 将得到 .o 打包成动态库, 使用gcc , 参数 -shared
    $ gcc -shared add.o div.o mult.o sub.o -o libcalc.so

    # 检查目录中是否生成了动态库
    $ tree
    .
    ├── add.c
    ├── add.o
    ├── div.c
    ├── div.o
    ├── include
    │   └── `head.h ===> 和动态库一起发布
    ├── `libcalc.so ===> 生成的动态库
    ├── main.c
    ├── mult.c
    ├── mult.o
    ├── sub.c
    └── sub.o

     第三步:发布生成的动态库和相关的头文件

    # 3. 发布库文件和头文件
    1. head.h
    2. libcalc.so

    动态库的使用

    当我们得到了一个可用的动态库之后,需要将其放到一个目录中,然后根据得到的头文件编写测试代码,对动态库中的函数进行调用。

    # 1. 拿到发布的动态库
    `head.h libcalc.so
    # 2. 基于头文件编写测试程序, 测试动态库中提供的接口是否可用
    `main.c`
    # 示例目录:
    .
    ├── head.h ==> 函数声明
    ├── libcalc.so ==> 函数定义
    └── main.c ==> 函数测试

    编译测试程序

    # 3. 编译测试程序
    $ gcc main.c -o app
    /tmp/ccwlUpVy.o: In function `main':
    main.c:(.text+0x38): undefined reference to `add'
    main.c:(.text+0x58): undefined reference to `subtract'
    main.c:(.text+0x78): undefined reference to `multiply'
    main.c:(.text+0x98): undefined reference to `divide'
    collect2: error: ld returned 1 exit status

    错误原因:

    和使用静态库一样,在编译的时候需要指定库相关的信息: 库的路径 -L 和 库的名字 -l

    添加库信息相关参数,重新编译测试代码:

    # 在编译的时候指定动态库相关的信息: 库的路径 -L, 库的名字 -l
    $ gcc main.c -o app -L./ -lcalc

    # 查看是否生成了可执行程序
    $ tree
    .
    ├── app # 生成的可执行程序
    ├── head.h
    ├── libcalc.so
    └── main.c

    # 执行生成的可执行程序, 错误提示 ==> 可执行程序执行的时候找不到动态库
    $ ./app
    ./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

    关于整个操作过程的报告:

    ​ gcc 通过指定的动态库信息生成了可执行程序,但是可执行程序运行却提示无法加载到动态库。



    优缺点
     
    静态库

    优点:

    1)静态库被打包到应用程序中加载速度快
    2)发布程序无需提供静态库,移植方便

    缺点:

    1)相同的库文件数据可能在内存中被加载多份,消耗系统资源,浪费内存
    2)库文件更新需要重新编译项目文件,生成新的可执行程序,浪费时间。

     

      动态库
     

    优点:

    1)可实现不同进程间的资源共享
    2)动态库升级简单,只需要替换库文件,无需重新编译应用程序
    3)程序猿可以控制何时加载动态库,不调用库函数动态库不会被加载

    缺点:

    1)加载速度比静态库慢,以现在计算机的性能可以忽略
    2)发布程序需要提供依赖的动态库

     
     
     
     
     
     
     
  • 相关阅读:
    Linux中怎么通过PID号找到对应的进程名及所在目录
    MYSQL 1093 之You can't specify target table for update in FROM clause解决办法
    Spring注解@Resource和@Autowired区别对比
    Java数据类型和MySql数据类型对应一览
    java高分局之jstat命令使用(转)
    为python安装matplotlib模块
    Python中的文件IO操作(读写文件、追加文件)
    Python 3语法小记(九) 异常 Exception
    SpringBoot下的Job定时任务
    linux的top命令参数详解
  • 原文地址:https://www.cnblogs.com/LaiY9/p/14688682.html
Copyright © 2020-2023  润新知