动态和静态链接库

函数库是现有的、可复用的代码。从本质上讲,库是一种可执行代码的二进制形式,可被操作系统载入内存执行。实际上,库就是一些头文件(.h)和库文件(.a、.so 或 .lib、.dll)的集合。Linux 将头文件和库文件分别放在 /usr/include/ 和 /usr/lib/ 目录。在某些时候程序需要的库不在这些目录下,此时需要在编译时指定所需的头文件和库文件的路径。

函数库分为静态链接库 (.a、.lib) 和动态链接库 (.so、.dll) 两种。所谓静态、动态是指链接时的状态,它们的区别在于“链接阶段”如何处理库,以链接生成可执行程序。

.a 和 .so 后缀名表示 Linux 中的静态库和动态库。

.lib 和 .dll 后缀名表示 Windows 中的静态库和动态库,

静态库的名字一般为 libxxx.a,其中 xxx 是此库的名称。

动态库的名字一般为 libxxx.so.major.minor,xxx 是该库的名称,major 是主版本号, minor 是副版本号。

gcc 在链接阶段优先使用动态链接库,只有动态链接库不存在时,才会使用静态链接库。

使用 -static 选项编译时会强制使用静态链接库。

实验文件

hello.h     hello 函数的定义文件
hello.c     hello 函数的实现文件
main.c      主程序

hello.h

#include <stdio.h>

void hello();

hello.c

#include "hello.h"

void hello() {
    printf("hello world\n");
}

main.c

#include "hello.c"

int main(int argc, char *argv[]) {
    hello();
}

静态链接库

静态库在链接阶段会将汇编生成的目标文件(.o 文件)与引用到的库一起链接打包生成可执行文件,这种链接方式即静态链接,此时生成的可执行程序的体积较大。由于静态库与汇编生成的目标文件一起链接成为可执行文件,因此静态库和 .o 文件相似。

实际上,静态库是一组目标文件 (.o/.obj 文件) 的集合,即多个目标文件经过打包压缩后生成的一个文件。

静态库的后缀是 .a,它的产生分为两步:

  • 由源文件编译生成一堆 .o 文件,每个文件都包含这个编译单元的符号表。
  • ar 命令将一个或多个 .o 文件转换成一个 .a 文件,即成为静态库。

生成 hello.o 文件

$ gcc -c hello.c

生成静态库文件 libhello.a

$ ar rcs libhello.a hello.o

生成可执行程序 main

$ gcc -o main main.c -L. -lhello
  • -c 选项:生成 obj 文件
  • -L 选项:指定编译时搜索的路径
  • -l 选项:指定编译时使用的库

静态库在链接时的路径搜索顺序

  • ld 首先会去查找 gcc 命令中的参数 -L
  • 然后查找 gcc 的环境变量 LIBRARY_PATH 指定的路径
  • 然后再查找系统指定目录 /lib、/usr/lib 以及 /usr/local/lib(这在编译时由 gcc 写到程序内)。

静态库的特点:

  • 便于移植

    由于静态库在链接阶段已被加入到可执行文件中,因此程序运行时不再需要该静态库,这使得程序的移植很方便。

  • 浪费空间和资源

    所有相关的目标文件与涉及的函数都被链接到了可执行文件,因此可执行文件的体积较大。此外,若运行了很多程序,且这些程序都使用了同一个库函数,那么在内存中会大量拷贝这个库函数,这造成了内存和存储空间的浪费

动态链接库

动态库在编译链接阶段只将对它的引用链接到目标代码中,在程序运行时,最终的函数导入内存开始执行,函数引用被解析,此时共享库代码才被载入内存。这使得生成的可执行文件体积较小。

若不同的应用程序调用相同的共享库,那么在内存里只需维持一份该共享库的实例即可。这使得内存空间的使用大大减少。共享函数库的另一个优点是,它可以独立更新,不会影响调用它的函数。

动态库的后缀是 .so,它由 gcc 加特定参数编译产生。系统加载可执行代码时候,可自动知道其所依赖库的名字,但是并不知道库的绝对路径,此时就需要系统动态载入器 (dynamic linker/loader)。

对于 elf 格式的可执行程序,路径查找是由 ld-linux.so 来完成,它依次搜索 elf 文件的:

  • 编译目标代码时指定的动态库搜索路径(DT_RPATH 段)
  • LD_LIBRARY_PATH 环境变量
  • /etc/ld.so.cache 文件
  • /lib/ 目录
  • /usr/lib 目录

找到库文件后将其载入内存。

新安装的库若在 /lib 或者 /usr/lib 下,那 ld 默认能够找到。若在其他目录,则需添加到 /etc/ld.so.cache 文件

  • 编辑 /etc/ld.so.conf 文件,加入库文件所在目录的路径
  • 运行 ldconfig,该命令会重建 /etc/ld.so.cache 文件

使用 ldd 命令可以查看一个可执行程序依赖的共享库。

生成动态库文件 libhello.so

$ gcc -fPIC -c hello.c
$ gcc -shared -o libhello.so hello.o

生成可执行文件 main

$ gcc -o main main.c -L. -lhello

此时若执行 main 程序会报错

$ ./main
./main: error while loading shared libraries: libdyn.so: cannot open shared object file: No such file or directory

一种方法是将环境变量 LD_LIBRARY_PATH 设置为当前目录(程序所在的目录)。

$ export LD_LIBRARY_PATH=$(pwd)

另一种方法是将 libhello.so 文件放到 /usr/lib,目录,并使用 ldconfig 命令重建 /etc/ld.so.cache 文件(非必须)。

$ cp libhello.so /usr/lib
$ ldconfig /usr/lib

区别

动态库和静态库的区别:

  • 在链接阶段,采用何种方式与汇编产生的目标文件一起生成可执行文件
  • 函数库与目标文件一起生成可执行文件
  • 函数库的引用于目标文件一起生成可执行文件

动态库的优缺点:

  • 占用的空间和资源小,多个程序可使用一个共享库实例
  • 移植性差,程序运行时需要动态库

静态库的优缺点:

  • 占用空间和资源大,每个程序都需要独自的静态库实例
  • 移植性好,可执行文件生成后就不再需要静态库

猜你喜欢

转载自www.cnblogs.com/reghao/p/9418977.html
今日推荐