0%

静态库和动态库制作

什么是库

库文件是计算机上的一类文件,提供给使用者一些开箱即用的变量、函数或类。静态库和动态库,静态库和动态库的区别体现在程序的链接阶段:静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。

假设当前目录下有以下文件

├── main.c

├── tools.c

└── tools.h

tools.h 包含get_sum函数的声明

1
2
3
4
#ifndef tools_h
#define tools_h
int get_sum(int a,int b);
#endif /* tools_h */

tools.c 包含get_sum函数的定义

1
2
3
int get_sum(int a, int b){
return a+b;
}

main.c 需要使用到get_sum函数

1
2
3
4
5
6
7
#include <stdio.h>
#include "tools.h"

int main(){
printf("%d\n",get_sum(12,12));
return 0;
}

静态库

在程序的链接阶段被复制到了程序中,编译完后就不再需要引入的库了。

命名规则

在Linux平台下:libxxx.a

​ libxxx.a:库文件的名字

​ lib:前缀(固定)

​ xxx:库的名字,自己起

​ .a:后缀(固定)

windows:libxxx.lib

制作方法

tools.c

1
2
3
int get_sum(int a, int b){
return a+b;
}

tools.h

1
2
3
4
#ifndef tools_h
#define tools_h
int get_sum(int a,int b);
#endif /* tools_h */

上述源文件包含一个求和函数,现在我们想把它打包成一个库供其他程序使用。

  1. 获得.o文件

    使用-c参数,只生成汇编程序,不进行链接

    1
    gcc -c tools.c
  2. 将.o文件打包,使用ar工具(archive)

    1
    ar rcs libtools.a tools.o

    r:将文件插入备存文件中,即libtools.a

    c:create,创建备存文件

    s:缩引

使用方法

此时文件结构:

├── libtools.a

├── main.c

├── tools.c

├── tools.h

└── tools.o

为了测试调用库中的文件,现在删除tools.c,tools.o,但不能删除tools.h,使用静态库来编译程序的时候需要用到,tools.h中保存了库文件中函数的定义。

现在目录结构:

├── libtools.a

├── main.c

└── tools.h

现在使用库文件libtools.a来编译main.c,需要指定-I(大i),-l -L参数

-I(大i,Include):指定头文件路径

-l :库的名字

-L:库文件的路径

1
$ gcc main.c -o app -I ./ -l tools -L ./

动态库

命名规则

Linux:libxxx.so

​ lib:前缀(固定)

​ xxx:库的名字

​ .so:后缀(固定)

​ 在Linux下是一个可执行文件

windows:libxxx.dll

制作方法

假设此时目录结构:

├── main.c

├── tools.c

└── tools.h

内容和1、一致

  1. 获得和位置无关的.o文件,需要添加 -fpic参数,在某些平台可能需要-fPIC

    1
    $ gcc -c -fpic tools.c

    生成tools.o

  2. 获得动态库

    1
    $ gcc -shared tools.o -o libtools.so

现在动态库就制作好了,此时目录结构:

├── libtools.so

├── main.c

├── tools.c

├── tools.h

└── tools.o

使用方法

  1. 编译main.c

    -I(大i,Include):指定头文件路径

    -l :库的名字

    -L:库文件的路径

    1
    $ gcc main.c -o app -I ./ -l tools -L ./
  1. 运行app

    1
    2
    3
    $ ./main
    $ ./app
    ./app: error while loading shared libraries: libtools.so: cannot open shared object file: No such file or directory

    此时报错了,原因是没有找到动态库,此时需要手动置顶动态库的地址。

  1. ldd,该命令可以查看一个程序的依赖关系,即需要导入哪些动态库

    1
    2
    3
    4
    5
    $ ldd ./app
    linux-vdso.so.1 (0x00007ffdab47f000)
    libtools.so => not found
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd4861ad000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fd4867a0000)

    可以看到,运行该程序需要libtools.so,此时显示未找到。

  1. 指定动态库路径

    假设动态库所在路径为/home/so

    临时指定,只在当前终端有效,关闭了或其他终端都不可用。

    1
    $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/so

    长期指定

    1
    2
    3
    4
    # 将上述命令写入用户的配置文件.bashrc,这样每次打开新的终端都会执行上面的代码
    vim ~/home/.bashrc
    # 在打开的文件最后写入 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/so
    # 保存退出
  1. 再次运行app

    1
    2
    3
    4
    5
    6
    7
    8
    $ ./app
    24
    $ ldd app
    $ ldd ./app # 查看app的依赖
    linux-vdso.so.1 (0x00007fff4c1b5000)
    libtools.so => /home/so/libtools.so (0x00007f470e7fd000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f470e40c000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f470ec01000)

原理

  • 静态库:GCC进行链接时,将静态库中的代码打包到可执行程序中

  • 动态库:GCC进行链接时,动态库的代码不会被打包到可执行程序中,只包含依赖

  • 程序启动之后,动态库会被动态加载到内存中,通过ldd(list dynamic dependencies)命令检查动态库依赖关系。

  • 如何定位共享库文件呢?

    当系统加载可执行程序时,能够指导其所依赖的库的名字,但是还需要知道绝对路径,此时需要系统的动态载入器来获取动态库的绝对路径。

    对于elf格式的可执行程序,有ld-linux.so来完成,它先后搜索DT_RPATH段,环境变量LD_LIBRARY_PATH,/etc/ld.so.cache文件列表,/lib/,/usr/lib目录找到库文件后将其载入内存。

区别

截屏2021-05-25 下午4.04.43

静态库

截屏2021-05-25 下午4.05.23

优点

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

缺点

  • 当多个程序需要使用用同一个库文件运行时,需要同时加载多个库文件,消耗系统资源,浪费内存。如下图所示:

    截屏2021-05-25 下午4.09.42
  • 更新,部署,发布麻烦

动态库

截屏2021-05-25 下午4.13.16

优点

  • 可以实现进程间资源共享(共享库)

    截屏2021-05-25 下午4.15.58
  • 更新,部署,发布简单

  • 可以控制何时加载动态库

缺点

加载速度比静态库慢

发布程序时,需要提供依赖的动态库