当前位置: 首页 > news >正文

Linux动态库与静态库解析

文章目录

  • 一、引言
  • 二、C/C++源文件的编译过程
  • 三、静态库
    • 1、静态库的定义和原理
    • 2、静态库的优缺点
    • 3、静态库的创建和使用
      • a、创建静态库
      • b、使用静态库
  • 四、动态库
    • 1、动态库的定义和原理
    • 2、动态库的优缺点
    • 3、动态库的创建和使用示例
      • a、创建动态库
      • b、使用动态库
  • 五、动静态库的比较

一、引言

在Linux系统开发中,库文件扮演着至关重要的角色。它们提供了程序运行所需的各种功能,使得开发者能够高效地复用代码,减少重复劳动。库文件通常分为动态库(也称为共享库)和静态库两种类型,它们在程序链接和运行阶段扮演着不同的角色。


二、C/C++源文件的编译过程

当我们使用gcc来编译一个C源文件,会经历如下过程:

在这里插入图片描述

从图中我们可以看出GCC处理HelloWorld.c的大致过程:预处理—>编译—>汇编—>链接。

下面我们详细解释该过程:

  1. 预处理(Preprocessing)

    • 预处理阶段会做一些文本操作。注释的删除、#include头文件的包含、#define符号的替换等。#define#include称为预处理指令。所有的预处理的指令都是此过程进行。

    • 在这个阶段,编译器会处理源代码文件HelloWorld.c中的预处理指令,如#include#define等。

    • #include <stdio.h>这样的指令会告诉编译器将标准输入输出头文件(stdio.h)的内容包含到源文件中。

    • 我们通常使用-E选项来只执行预处理阶段,并将输出重定向到一个文件。例如:

      gcc -E HelloWorld.c -o HelloWorld.i
      

      预处理后的输出文件通常是.i扩展名,如HelloWorld.i

  2. 编译(Compilation)

    • 编译阶段将预处理后的文件转换成汇编代码。且对预处理后的文件进行语法分析、词法分析、语义分析、符号汇总,并生成汇编代码。

    • 我们通常使用-S选项来只执行编译阶段,生成汇编代码。例如:

      gcc -S HelloWorld.i -o HelloWorld.s
      

      如果直接从.c文件编译到.s文件,命令如下:

      gcc -S HelloWorld.c -o HelloWorld.s
      

      汇编代码通常以.s为扩展名。如HelloWorld.s,该文件包含了汇编代码。

  3. 汇编(Assembly)

    • 汇编阶段将汇编语言文件转换成机器语言的目标文件。把汇编代码翻译成二进制指令,生成的是目标文件,目标文件中存放的都是二进制的指令。即形成符号表。

    • 我们通常使用-c选项来执行汇编阶段,生成目标文件(object file)。

      gcc -c HelloWorld.s -o HelloWorld.o
      

      注意:同样地,通常会直接从.c.s文件汇编到.o文件。从.s文件汇编的命令如上所示。

      如果直接从.c文件汇编到.o文件,命令如下:

      gcc -c HelloWorld.c -o HelloWorld.o
      

      目标文件通常以.o为扩展名。如HelloWorld.o

  4. 链接(Linking)

    • 链接阶段是将目标文件(.o文件)与所需的库文件合并起来,生成最终的可执行文件。这个过程会进行合并段表、符号表的合并和重定位等。链接器会解析目标文件中的外部符号引用(如函数调用),并将它们与库文件中的定义连接起来。最终生成的可执行文件。

    • 使用gcc命令(不带-c-S-E等选项)来执行链接阶段,生成可执行文件。如果程序使用了任何标准库函数(如printf),则链接器会自动链接必要的库。例如:

      gcc HelloWorld.o -o HelloWorld
      

      或者,如果直接从.c文件开始,并且没有中间步骤,可以简单地使用:

      gcc HelloWorld.c -o HelloWorld
      

      这将生成一个名为HelloWorld的可执行文件,该文件包含了完整的机器码指令,可以直接运行在计算机上。


三、静态库

1、静态库的定义和原理

静态库 (Static Library) 是一种在编译时链接到程序中的库文件,它的格式为.a(在Linux系统中)。静态库包含了程序运行所需的所有代码和数据,当程序编译时,链接器会将静态库中的代码和数据直接复制到生成的可执行文件中。

我们观察使用了静态库的程序:

在这里插入图片描述

  1. 使用 ldd a.out 命令检查 a.out 的动态链接依赖时,输出了 not a dynamic executable,这是因为 a.out 是一个静态链接的可执行文件,它不依赖于任何外部的动态链接库。
  2. 使用 file a.out 命令查看文件类型时,输出描述了这是一个静态链接的 ELF 64 位可执行文件,并且没有动态链接器(如 /lib64/ld-linux-x86-64.so.2)的引用。

因此,使用静态库生成可执行程序后,该程序运行时就不依赖外部库文件,可独立运行,但生成的可执行文件体积较大。

在这里插入图片描述

  1. gcc -o test_static test.c -static

这个命令使用了 -static 选项将 test.c 编译并链接为一个名为 test_static 的可执行文件。-static 选项告诉链接器在创建可执行文件时,应该使用静态库而不是动态库。这意味着它在运行时不需要动态链接器来解析任何动态库依赖。

  1. gcc -o test_shared test.c

这个命令将 test.c 编译并链接为一个名为 test_shared 的可执行文件,且没有使用任何特殊的链接选项。默认情况下,链接器会使用动态库来解析程序中的依赖。这意味着 test_shared 在运行时需要加载它依赖的共享库。这种方法生成的可执行文件通常较小,因为它不包含它所依赖的库的完整副本,但它需要这些库在运行时可用。

因此,我们可以明显观察到两者的大小差距。

2、静态库的优缺点

优点:

  • 执行速度快: 由于代码已经包含在可执行文件中,不需要运行时动态链接,可以减少启动时间。即编译成功的可执行文件可以独立执行,而不需要再向外部要求读取函数库的内容 。
  • 确定性: 程序的行为不会受到其他程序使用相同库的不同版本的影响。

缺点:

  • 占用空间: 每个使用静态库的可执行文件都会包含库的一份副本,导致文件体积增大。
  • 更新困难: 当静态库更新时,所有使用该库的程序都需要重新编译和链接。
  • 资源浪费: 如果多个程序使用相同的静态库,那么每个程序中都会有一份库的副本,浪费内存和磁盘空间。

3、静态库的创建和使用

a、创建静态库

在这里插入图片描述

下面我们根据上图来介绍我们的静态库创建过程,我们有两个C源文件,mymath.cmystdio.c,以及他们对于的.h文件。

  1. 编译源文件为目标文件

使用gcc编译器将源文件编译为目标文件。目标文件是包含机器代码但尚未链接的文件。

gcc -c mymath.c 
gcc -c mystdio.c 

在这里插入图片描述

这将生成mystdio.omymath.o两个目标文件。

  1. 创建静态库

使用ar工具将目标文件打包成静态库。在Linux系统中,静态库通常以.a为扩展名。

ar -rc libmylib.a mymath.o mystdio.o

在这里插入图片描述

这将创建一个名为libmylib.a的静态库。使用 ar 命令来创建一个静态库文件 libmylib.a,这个库文件包含了 mymath.omystdio.o 这两个目标文件。我们分析该命令:

  • ar:这是GNU归档器命令,用于创建、修改和提取静态库文件(通常是 .a 文件)。
  • -r:替换现有的目标文件或添加新的目标文件到归档文件中。(replace)
  • -c:创建一个归档文件,如果它不存在的话。这个选项与 -r 一起使用时,意味着如果归档文件已经存在,则替换其中的同名目标文件,如果归档文件不存在,则创建一个新的归档文件。(create)

b、使用静态库

  1. 编写主程序

编写一个主程序(例如myprogram.c),它调用静态库中的函数。在程序中,我们需要包含定义库函数的头文件,并在链接时指定库文件。

  1. 编译和链接主程序

使用gcc编译器编译主程序,并在链接时指定静态库,以便使用 mymath.omystdio.o 中定义的函数或变量。在链接时,我们需要告诉编译器和链接器静态库文件的路径和名称,通常使用 -L-l 选项。例如:

gcc -o myprogram myprogram.c -L /path/to/lib_test -lmylib -I  /path/to/lib_include

这里,-L告诉编译器在哪个目录下搜索库文件,这里的 /path/to/lib_test 应该替换为实际存放 libmyc.a 的路径。注意 -l 选项后面跟的是库名(不包括前缀 lib 和后缀 .a)。-L选项后跟的是存放库的路径。-I告诉编译器在哪个目录下搜索头文件,这里的 /path/to/lib_include 应该替换为实际存放所使用头文件的路径。

下面我们进行实验,我们编写如下用于构建并组织构建静态库libmylib.a的makfile:

#形成静态库
libmylib.a:mymath.o mystdio.oar -rc $@ $^%.o:%.cgcc -c $< .PHONY:clean
clean:rm -rf *.o  mylib *.a.PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/libcp ./*.h mylib/includecp ./*.a mylib/lib

这个makefile定义了两个目标(静态库和对象文件)的构建规则,以及两个伪目标(cleanoutput)用于清理文件和组织构建的输出。

在这里插入图片描述

此时我们已经构建好当前的静态库,现在该目录下就只有test.c和我们刚才打包好的静态库。

在这里插入图片描述

首先我们使用gcc来编译我的C源文件:

在这里插入图片描述

图中错误表示,编译器在编译 test.c 文件时,它找不到名为 mymath.h 的头文件。 gcc 在编译 test.c 时找不到 mymath.h 头文件。因此我们需要在编译时告诉 gcc 在哪里可以找到这些头文件。我们使用-I选项,告诉编译器在哪个目录下搜索头文件:

在这里插入图片描述

图中错误表示,当编译器编译 test.c 时,它看到了对 myAdd 函数的调用(很可能是在 main 函数中),但是在链接阶段,链接器找不到这个函数在何处定义。 -l-L 选项来告诉链接器链接到 libmylib.a 静态库。-l 选项后面跟着库名(不带前缀 lib 和后缀 .a),而 -L 选项后面跟着库文件的搜索路径。

在这里插入图片描述

注意

在Linux系统上,动态库通常以.so为扩展名,静态库通常以.a为扩展名。这些库文件在命名时,通常会遵循一个特定的模式,即前缀lib,然后是库名,再是可选的版本信息,最后是文件扩展名(.so.a)。

例如:

在这里插入图片描述

  1. libyaml-0.so.2 是一个为软链接,它指向实际的库文件 libyaml-0.so.2.0.6
  2. libyaml-0.so.2.0.6 是实际的库文件。

当我们谈论“库名”时,我们通常指的是去掉前缀lib、扩展名(.so.a.so.版本号)以及任何版本信息之后的部分。因此,在这个例子中:

  • 符号链接名(或称为“库引用名”):libyaml-0.so.2
  • 实际库文件名:libyaml-0.so.2.0.6
  • 库名(不包括版本):libyaml

注意,库名 libyaml 是从文件名 libyaml-0.so.2libyaml-0.so.2.0.6 中去掉 lib 前缀、.so.so.版本号 后缀后得到的。这是因为在编写代码并链接到库时,通常会使用不包括这些前缀和后缀的库名。例如,在 C 或 C++ 中,会使用 -lyaml 来链接到 libyaml 库。

在链接程序时,我们通常只需要指定库名(不包括前缀lib和后缀),链接器会自动在系统的库路径(如/lib/usr/lib等)中查找相应的库文件。


四、动态库

1、动态库的定义和原理

动态库 (Dynamic Library) 是一种在程序运行时可以动态加载的库。它的格式为.so (Shared Object) 在 Linux 系统中,动态库也称为共享库。

动态库是一种在程序运行时可以动态加载的库。这意味着动态库的内容(包括函数、变量和类等)并不在编译时被包含进程序本身,而是在程序运行时才根据需要被加载。因此,多个程序可以共享同一个动态库,从而节省内存空间。

我们来观察使用了动态库的程序:

在这里插入图片描述

  1. 使用 ldd 命令检查了 a.out 的动态链接依赖,输出显示它依赖于 libc.so.6(C标准库)和 ld-linux-x86-64.so.2(动态链接器)。
  2. 使用 file 命令查看了 a.out 的文件类型信息,输出详细描述了它是一个64位的位置无关可执行文件(PIE),动态链接的,并且为 GNU/Linux 3.2.0 或更高版本设计。同时,输出还包含了构建ID(BuildID)和其他一些信息。

从这些信息中,我们可以确认。a.out 是一个动态链接的可执行文件,这意味着它在运行时需要依赖其他动态库(如 libc.so.6)。

那么动态库的原理是什么呢?

  • 实时加载:当程序需要调用动态库中的函数或变量时,操作系统会实时加载动态库到内存中。这样,只有当程序实际需要某个库时,才会占用相应的内存资源。
  • 共享性:由于动态库是在程序运行时被加载的,因此多个程序可以共享同一个动态库。这意味着,如果多个程序都使用了同一个动态库,那么这些程序在运行时只需要加载一份动态库到内存中,从而节省了内存空间。
  • 动态链接:在程序运行时,操作系统会将程序与动态库进行动态链接。这种链接方式允许程序在运行时根据需要加载或卸载动态库,从而提高了程序的灵活性和可维护性。

动态库是程序在运行的时候才去链接相应的动态库代码的,多个程序共享使用库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。

在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接。动态库在多个程序间共享,节省了内存空间,操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存。

下面我们来理解动态库的使用过程:

使用了动态库的C/C++源文件需要通过编译链接才能被使用。那么我们通常使用gcc来编译链接得到可执行程序,然后操作系统来运行它的可执行程序。

编译时的搜索路径——gcc需要

在编译程序时,如果程序引用了动态库中的函数或数据,编译器(如gcc)需要知道这些函数或数据在哪些动态库中存在。这通常通过包含头文件(.h文件)来实现,这些头文件声明了库中的函数和数据,并可能还包含了指定库文件名的预处理器指令。

然而,仅仅知道函数和数据的声明是不够的,编译器还需要知道这些函数和数据在动态库中的实际定义。这通常通过编译器选项来指定,我们将在后文详细描述该过程的实现方法。

因此,在编译时,编译器(如GCC)需要知道在哪里可以找到所需的动态库的头文件(.h 文件)以及用于链接的库文件。

运行时的库搜索路径——操作系统需要

当程序运行时,操作系统需要找到程序所依赖的动态库文件,并将它们加载到内存中,以便程序能够调用库中的函数。操作系统会按照一定的搜索顺序来查找这些库文件。

如果操作系统在所有这些路径中都没有找到所需的动态库文件,程序就会因为找不到依赖的库而无法正常运行。

编译是编译器的事情,运行是os的事情

这句话的意思是,编译程序是编译器的责任,编译器需要确保程序在语法和语义上是正确的,并且所有引用的函数和数据都有定义。而运行程序则是操作系统的责任,操作系统需要确保程序所需的所有资源(包括动态库文件)都是可用的,并且程序能够正确地执行。

编译器和操作系统在程序的生命周期中扮演着不同的角色,但它们之间需要密切合作,以确保程序能够正确地编译和运行。

编译与运行的区别

  • 编译:是源代码到可执行文件或库的转换过程,由编译器完成。编译器处理源代码,检查语法和语义,生成机器代码或中间代码,并可能链接库以创建最终的可执行文件或库。
  • 运行:是执行编译后的代码的过程,由操作系统管理。操作系统加载程序到内存中,解析程序中的动态链接,执行程序,并处理程序运行时的各种请求和事件。

因此,编译时关注的是源代码和库的静态关系,而运行时关注的是程序与操作系统的动态交互。动态库在这两个过程中都扮演着重要角色,但需要在不同的阶段以不同的方式进行处理。

综上所述,我们可以得出下面的结论:运行时不需要头文件,只需要动态库。编译需要头文件和库。

在编译一个程序时,编译器需要头文件来解析函数、类和其他实体的声明,以确保源代码中使用的所有标识符都是已定义和可用的。此外,如果程序依赖于某个库中的函数或类,那么编译器还需要这个库的头文件,以便知道如何正确地链接到这些库中的函数或类。

但是,一旦程序被编译并链接成可执行文件,它就不再需要头文件了。在运行时,操作系统加载并执行这个可执行文件,它只需要与可执行文件相关联的动态库(如果有的话)。这些动态库包含了程序在运行时需要调用的函数和类的实现。

所以,简而言之:

  • 编译时:需要头文件和库(静态库或动态库的头文件)。
  • 运行时:只需要动态库(如果程序依赖于它们)。

注意,静态库在编译时会被链接到可执行文件中,因此运行时不需要额外的静态库文件。而动态库在运行时由操作系统加载到内存中,并与可执行文件一起使用。

下面我们再从进程地址空间的角度来理解:

首先我们理解一下动态库的本质:在多个系统进程中共享公共的代码和数据,只需要存一份

在这里插入图片描述

当程序使用动态库时,操作系统会在内存中为动态库分配一块共享区域(即上图中的共享区),并将该区域映射到所有使用该动态库的进程的地址空间中。这样,当多个进程同时访问动态库中的函数或数据时,它们实际上是在访问同一块内存区域。这种共享机制使得动态库成为了一种非常高效的代码和数据复用方式。

动态库通常存放在系统的特定目录下,如 /usr/lib/usr/local/lib/lib。这些目录会被动态链接器(如 ld-linux.so)和操作系统搜索以找到需要的库文件。

动态库加载过程

  1. 加载到进程地址空间:当程序尝试调用动态库中的函数时,动态链接器会将该库加载到进程的地址空间中,即上图的“共享区”。
  2. 符号解析:动态链接器会解析程序引用的动态库中的符号(如函数和数据)。它会在动态库的符号表中查找这些符号的地址,并在程序的地址空间中建立相应的映射。
  3. 调用库函数:当程序调用动态库中的函数时,它会跳转到地址空间中的共享区,执行库中的函数代码。执行完成后,程序会跳转回原来的位置,继续执行。
  4. 页表映射:为了使得程序能够正确地访问这个共享区中的代码和数据,操作系统会在页表中建立相应的条目,将动态库的虚拟地址映射到这块共享区的物理地址。并在每个需要使用该动态库的进程的页表中建立相应的映射条目。这样,当程序访问动态库中的函数或数据时,处理器就可以通过页表找到对应的物理地址,从而实现对动态库的访问。

因此,不论多少程序使用,内存中只会存在该动态库指令的一份拷贝,实现了代码共享;在程序运行时才会去引用库中的相关函数,并不把这些函数的指令包含进去。

理解动态库动态链接和加载

  • 动态链接:在程序运行时,动态链接器将程序所需的动态库链接到程序的虚拟地址空间中。这样,程序就可以在运行时动态地加载和使用动态库中的函数和数据。
  • 动态加载:动态加载允许程序在运行时根据需要加载动态库。这意味着程序可以在启动时只加载必要的代码和数据,然后在需要时动态地加载其他代码和数据。这有助于提高程序的启动速度和灵活性。
  • 动态链接和加载的优点包括:节省内存(因为多个程序可以共享同一个动态库)、支持多语种(因为动态库可以与不同的编程语言一起使用)、可重复利用(因为动态库可以被多个程序共享)以及便于大项目的开发(因为可以将功能拆分成多个独立的动态库)。

虚拟地址空间不仅是操作系统要遵守的,编译器在编译程序时也需要遵守。

进程地址空间是多任务操作系统中每个进程所拥有的独立的、隔离的内存环境。在操作系统中,每个进程都运行在属于自己的地址空间。操作系统通过虚拟地址空间来管理物理内存和磁盘空间,实现内存的保护和隔离,确保每个进程都有独立的内存空间,并且只能访问自己的内存空间,不能访问其他进程的内存空间。

编译器在编译程序时,也需要遵守虚拟地址空间的规则。编译器将源代码转换为机器代码时,会生成程序所需要的虚拟地址空间布局。编译器需要确保程序在运行时能够正确地访问和操作虚拟地址空间中的数据和代码。编译器还需要考虑虚拟地址空间和物理地址空间之间的映射关系,以及如何处理内存不足等问题。

因此,虚拟地址空间是操作系统和编译器都需要遵守的重要概念。操作系统通过虚拟地址空间来管理内存和隔离进程,而编译器则通过虚拟地址空间来生成可执行的程序代码,并确保程序能够正确地访问和操作虚拟地址空间中的数据和代码。

2、动态库的优缺点

优点

  1. 代码重用:动态库允许多个程序共享使用相同的代码和数据,从而减少了内存的使用并提高了代码的重用性。
  2. 资源节约:由于多个程序可以共享使用同一个动态库,因此不需要在每个程序中都包含相同的代码和数据,从而节省了磁盘空间。
  3. 更新和维护方便:当动态库更新时,所有使用该库的程序都可以立即受益,而无需重新编译或重新分发程序本身。这大大简化了软件的更新和维护过程。

缺点

  1. 加载时间:由于动态库在程序运行时需要被加载到内存中,因此相对于静态链接的程序来说,动态链接的程序在启动时可能会有一定的延迟。
  2. 依赖问题:动态链接的程序依赖于外部的动态库文件。如果这些库文件不存在、版本不匹配或路径不正确,程序将无法正常运行。这增加了程序部署和管理的复杂性。

3、动态库的创建和使用示例

a、创建动态库

在这里插入图片描述

上图中,执行一系列步骤来创建一个动态库文件。该文件可以在多个程序之间共享,而不需要在每个程序中都包含相同的代码。

  1. 编译 .c 为 .o
gcc -c -fPIC mystdio.c
gcc -c -fPIC mymath.c

这两条命令使用 gcc来编译 *.c 源文件。-c 选项告诉 gcc 只编译源文件但不进行链接。-fPIC 选项告诉编译器生成位置无关代码(Position Independent Code),这是创建共享库所必需的。编译后的输出是一个名为 *.o 的目标文件。

  1. 链接 mystdio.o 和 mymath.o 为 libmyc.so
gcc -shared -o libmyc.so mystdio.o mymath.o

这条命令使用 gcc-shared 选项来创建一个共享库。-o libmyc.so 指定了输出文件的名称,即 libmyc.so。然后,命令列出了要链接的所有目标文件:mystdio.omymath.o。链接器将这些目标文件组合成一个共享库,该库可以在运行时由多个程序共享。

我们编写如下用于构建并组织构建静态库libmyc.so的makfile:

libmyc.so:mymath.o mystdio.ogcc -shared -o $@ $^
%.o:%.cgcc -c -fPIC $< 
# mymath.o:mymath.c
# 	gcc -c -fPIC $< # mystdio.o:mystdio.c
# 	gcc -c -fPIC $< .PHONY:clean
clean:rm *.o  libmyc.so

b、使用动态库

现在,可以在其他C或C++程序中链接这个静态库,以便使用 mymath.omystdio.o 中定义的函数或变量。在链接时,需要告诉编译器和链接器静态库文件的路径和名称,通常使用 -L-l 选项(对于gcc和g++)。

  1. 编译时链接

在编译程序时,需要在编译命令中指定包含头文件的目录(如果有的话)和库文件的搜索路径。使用 -I 选项指定头文件搜索路径,使用 -L 选项指定库文件搜索路径,使用 -l 选项指定库名(不包含前缀 lib 和后缀 .so)。

gcc -o myprogram myprogram.c -I /path/to/headers -L /path/to/lib_test -lmyc

这里的 /path/to/lib_test 应该替换为实际存放 libmyc.so 的路径。/path/to/headers应该替换为实际存放 *.h 的路径。

  1. 运行时查找库

当程序运行时,操作系统需要知道在哪里可以找到 libmyc.so。这通常通过以下几种方式之一实现:

2.1 LD_LIBRARY_PATH 环境变量

可以将包含 libmyc.so 的目录添加到 LD_LIBRARY_PATH 环境变量中。例如:

export LD_LIBRARY_PATH=/path/to/lib_test:$LD_LIBRARY_PATH  
./myprogram

这种方法只影响当前终端会话。

2.2 将库安装到标准位置

如果希望多个程序都能使用这个库,可以将 libmyc.so 复制到标准库目录(如 /usr/lib/usr/local/lib)。这样,程序在系统启动时就可以自动找到库了。

sudo cp libmyc.so /usr/local/lib/   
./myprogram

注意:在复制库文件到系统目录之前,请确保拥有相应的权限,并且了解这可能对其他系统用户产生影响。

示例

假设您已经将 libmyc.so 放在了 /path/to/lib_test 目录中,并且您有一个 myprogram.c 文件,可以这样编译和运行它:

# 编译 myprogram.c 并链接到 libmyc.so  
gcc -o myprogram myprogram.c -L/path/to/lib_test -lmyc  # 设置 LD_LIBRARY_PATH 环境变量(仅在当前终端会话中有效)  
export LD_LIBRARY_PATH=/path/to/lib_test:$LD_LIBRARY_PATH  export LD_LIBRARY_PATH=/home/zyb/study_code/file_sys/lib_test/user
/mylib/lib:$LD_LIBRARY_PATH  # 运行程序  
./myprogram

或者,如果已经将库安装到了标准位置并更新了缓存,那么可以直接运行程序而无需设置 LD_LIBRARY_PATH

下面我们进行实验,我们编写如下用于构建并组织构建静态库libmyc.a的makfile:

#形成动态库
libmyc.so:mymath.o mystdio.ogcc -shared -o $@ $^
%.o:%.cgcc -c -fPIC $< 
.PHONY:clean
clean:rm -rf *.o  libmyc.so mylib .PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/libcp -rf *.h mylib/includecp -rf *.so mylib/lib

makefile与上文构建静态库类似,不再赘述。

下面我们来观察动态库动态搜索和链接过程

在这里插入图片描述

  1. 编译未链接库
    首先尝试编译main.c但没有链接到libmyc.so库。因此,链接器报告了在main.c中引用了未定义的函数myAddmy_fopen

    解决方案:在编译时添加-L选项指定库文件的位置,并使用-l选项指定库名(注意,库名不需要前缀lib和后缀.so)。

  2. 链接时未找到库
    在第二次尝试中,添加了-lmyc但忘记添加-L ./mylib/lib/来指定库文件的搜索路径。因此,链接器无法找到libmyc.so

    解决方案:在编译命令中同时添加-L-l选项。

  3. 运行时找不到库
    当尝试运行myexe时,系统报告找不到libmyc.so库。这是因为运行时链接器(动态链接器)没有在默认的库搜索路径中找到该库。

    解决方案:

    1、直接把动态库复制到默认的库搜索路径中。

    在这里插入图片描述

    2、添加到环境变量 LD_LIBRARY_PATH中。

    这个环境变量指定了动态链接器(如 ld-linux.so)在标准位置(如 /lib/usr/lib)之外搜索动态库(如 .so 文件在 Linux 上)的目录列表。当运行一个程序,并且该程序依赖于某个动态库时,动态链接器会首先查看 LD_LIBRARY_PATH 中列出的目录来查找这个库。如果找到了,则使用这个库;如果没有找到,则继续搜索标准位置。

    3、建立软链接

    假设动态库位于 /path/to/your/library/libmyserver.so,并且您想将它链接到系统的标准库目录(例如 /usr/lib)中。您可以使用 ln 命令来创建软链接。

    在这里插入图片描述

    在这里插入图片描述

    sudo ln -s /path/to/your/library/libmyserver.so /usr/lib/libmyserver.so
    

    4、设置系统配置文件

    /etc/ld.so.conf.d/ 目录在 Linux 系统中用于存放动态链接器的配置文件,这些文件告诉系统在哪里可以找到动态库。当系统需要加载一个共享库时,它会查看 /etc/ld.so.conf 文件(这个文件通常包含了指向 /etc/ld.so.conf.d/ 目录下所有配置文件的指令)以及 /etc/ld.so.conf.d/ 下的所有 .conf 文件,以确定库文件的搜索路径。

    如果有一个自定义的库文件路径,并且希望在系统启动或运行任何需要该库的程序时,动态链接器都能找到这个库,我们可以创建一个新的 .conf 文件在 /etc/ld.so.conf.d/ 目录下,添加我的库文件所在的目录路径。

    在这里插入图片描述

    添加完成后,运行 ldconfig 命令以更新动态链接器的缓存。这个命令会读取 /etc/ld.so.conf/etc/ld.so.conf.d/ 下的所有配置文件,并构建动态链接器的缓存。
    在这里插入图片描述

    删除配置文件后,ldd myexe就not found。


五、动静态库的比较

首先我们对它们的使用的特点进行对比:

  1. 编译和链接过程的比较:
    • 静态库:在编译阶段,静态库会被完全链接到可执行文件中,多个程序使用时,会有多份代码,所以代码体积会增大。这意味着当静态库更新时,所有使用它的程序都需要重新编译和链接。
    • 动态库:在编译阶段,程序只与动态库进行符号链接,并不将库的内容直接包含到可执行文件中。在运行时,操作系统动态地负责加载和链接动态库。因此,当动态库更新时,只需要替换库文件,而不需要重新编译和链接程序。
    • 静态链接表示静态性,在编译链接之后,库中需要的资源已经在可执行程序中了,也就是静态存在,没有依赖性了。而动态链接表示实时性,在运行的时候载入需要的资源,必须在运行的时候提供需要的动态库,有依赖性,运行时候没有找到库就不能运行了。
  2. 运行时性能的比较:
    • 静态库:由于静态库在编译时被完全链接到可执行文件中,因此程序的启动速度通常较快。但是,由于所有依赖的库都被包含在内,可执行文件可能会变得较大,从而占用更多的内存和磁盘空间。
    • 动态库:动态库在运行时被加载到内存中,因此程序的启动速度可能会稍慢一些。但是,由于多个程序可以共享同一个动态库,因此可以减少内存和磁盘空间的占用。此外,动态库支持延迟加载,即只在需要时才加载库中的代码和数据,这有助于进一步提高程序的性能。
  3. 适用场景的分析:
    • 静态库:适用于以下场景:
      • 程序只使用库中的部分函数,且这些函数的大小比较小。
      • 库的版本很少更改,且不需要与其他库进行交互。
      • 程序需要在没有库文件的情况下运行,或者需要将程序和库打包成单个可执行文件。
    • 动态库:适用于以下场景:
      • 库的大小较大,或者它包含在多个程序中。
      • 库的版本需要经常更改,或者需要与其他库进行交互。
      • 需要在运行时加载库(例如插件)。
      • 动态库在运行时并不会全部加载到内存中,而是以需要的方式进行加载。这种延迟加载可以帮助减少内存占用,并提高应用程序的性能。

综上所述,动静态库各有优缺点,适用于不同的场景和需求。在选择使用哪种库时,需要根据具体的项目需求和环境条件进行权衡和考虑。

那么若动态库和静态库同时存在,会发生什么呢?

在这里插入图片描述

我们可以分析出,如果同时有动态库和静态库,默认使用动态库。

在这里插入图片描述

若此时要使用静态链接,需要加上 -static选项。

下面我们把动态库移走,观察只有静态库的情况下会发生什么?
在这里插入图片描述

此时,我们移走了动态库,只剩下了静态库。那么只能对该库进行静态链接,但是程序不一定整体是静态链接的。

在这里插入图片描述

如果只有动态库,默认只能动态链接。若静态链接,会报错。

如果同时提供动态库和静态库,gcc默认使用动态库。如果想使用静态链接。需要加 static使用。如果只有静态库,那我们的可执行程序只能进行静态链接,但是程序不一定整体是静态链接的。如果只有动态库,默认只能动态链接,若非要静态链接,会发生链接报错。

相关文章:

Linux动态库与静态库解析

文章目录 一、引言二、C/C源文件的编译过程三、静态库1、静态库的定义和原理2、静态库的优缺点3、静态库的创建和使用a、创建静态库b、使用静态库 四、动态库1、动态库的定义和原理2、动态库的优缺点3、动态库的创建和使用示例a、创建动态库b、使用动态库 五、动静态库的比较 一…...

后端的一些科普文章

后端开发一般有4个方面 后端开发流程 1阶段 域名认证 是每一个计算机在网络上有一个ip地址&#xff0c;可以通过这个地址来访问102.305.122.5&#xff08;举例&#xff09;&#xff0c; 但是这个公网ip地址&#xff0c;比较难记忆&#xff0c;所以大家使用域名来更好的记忆…...

【Android学习】日期和时间选择对话框

实现功能 实现日期和时间选择的对话框&#xff0c;具体效果可看下图(以日期为例) 具体代码 1 日期对话框 1.1 xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android&quo…...

在Linux上使用Selenium驱动Chrome浏览器无头模式

大家好&#xff0c;我们平时在做UI自动化测试的时候&#xff0c;经常会用到Chrome浏览器的无头模式&#xff08;无界面模式&#xff09;&#xff0c;并且将测试代码部署到Linux系统中执行&#xff0c;或者平时我们写个爬虫爬取网站的数据也会使用到&#xff0c;接下来和大家分享…...

Feign 第一次调用为什么会很慢?

feign调用的大致过程&#xff1f; Feign进行远程调用的&#xff0c;这里面包括&#xff0c;注册中心、负载均衡、FeignClient之间的关系&#xff0c;微服务通过不论是eureka、nacos也好注册到服务端&#xff0c;Feign是靠Ribbon做负载的&#xff0c;而Ribbon需要拿到注册中心的…...

Spring Framework-简介

Spring Framework Java Spring是一个开源的Java应用框架&#xff0c;它的主要目的是简化企业级应用开发的复杂性。Spring框架为开发者提供了许多基础功能&#xff0c;使得开发者能够更专注于业务逻辑的实现&#xff0c;而不是底层的细节。 主要特点和功能&#xff1a; 控制反…...

Linux——PHP8.0编译安装和yum安装

文章目录 Linux——PHP8.0编译安装和yum安装PHP8.0编译安装PHP8.0yum安装 Linux——PHP8.0编译安装和yum安装 PHP8.0编译安装 此内容参考于&#xff1a;https://www.cnblogs.com/jhno1/p/14237034.html 安装依赖 # 安装编译依赖 [rootcsq ~]# yum -y install ncurses ncurses…...

【通义千问系列】Qwen-Agent 从入门到精通【持续更新中……】

目录 前言一、快速开始1-1、介绍1-2、安装1-3、开发你自己的Agent 二、Qwen-Agent的使用和开发过程2-1、Agent2-1-1、Agent使用2-1-2、Agent开发 2-2、Tool2-2-1、工具使用2-2-2、工具开发 2-3、LLM2-3-1、LLM使用2-3-2、LLM开发 三、基于Qwen-Agent的案例分析3-1、3-2、 总结 …...

1081:分苹果

1081&#xff1a;分苹果 时间限制: 1000 ms 内存限制: 65536 KB 提交数:65448 通过数: 54401 【题目描述】 把一堆苹果分给n个小朋友&#xff0c;要使每个人都能拿到苹果&#xff0c;而且每个人拿到的苹果数都不同的话&#xff0c;这堆苹果至少应该有多少个&#x…...

Linux—-vim基础使用

1、基本概念 Vim的工作模式有四种&#xff0c;普通模式&#xff0c;输入模式&#xff0c;命令模式&#xff0c;可视模式。 在终端中打开vim&#xff0c;只需要输入vim 文件&#xff0c;在普通模式下按i就会进入到输入模式&#xff0c;按下:进入命令模式&#xff0c;输入:q就可…...

《ESP8266通信指南》12-Lua 固件烧录

往期 《ESP8266通信指南》11-Lua开发环境配置-CSDN博客 《ESP8266通信指南》10-MQTT通信&#xff08;Arduino开发&#xff09;-CSDN博客 《ESP8266通信指南》9-TCP通信&#xff08;Arudino开发&#xff09;-CSDN博客 《ESP8266通信指南》8-连接WIFI&#xff08;Arduino开发…...

Covalent Network(CQT)通过 “新曙光” 计划实现重要里程碑,增强以太坊时光机,提供 30% 的年化质押收益率

Covalent Network&#xff08;CQT&#xff09;作为集成超过 280 条区块链&#xff0c;并服务于超过 2.8 亿个钱包的领先结构化数据基础设施层&#xff0c;宣布了其战略计划 “新曙光” 中的一个重要进展。随着网络升级并完成了准备工作的 75%&#xff0c;这将为即将部署的以太坊…...

JVM8参数设置相关

1、堆内存设置 JVM堆内存的设置大小应根据应用程序的具体需求和系统环境来确定。以下是一些常见的考虑因素和建议&#xff1a; 应用程序需求&#xff1a;如果应用程序需要处理大量数据或运行多个线程&#xff0c;那么可能需要更大的堆内存。反之&#xff0c;如果应用程序的需…...

送别PI-DataLink,行列视(RCV)完美替代

行列视&#xff08;RCV&#xff09;和 OSI PI 实时数据库自带的 PI-DataLink 报表工具&#xff0c;属于以实时数 据库为主要数据源的报表产品。 PI-DataLink 是专门针对 PI 实时数据库系统开发的一套 Excel 报表插件&#xff0c;功能强大&#xff0c;借助 Excel 自身的强大报表…...

【EasySpider】EasySpider+mysql执行配置异常

问题 使用易采集工具操作时候&#xff0c;遇到一个执行异常&#xff0c;后来发现没有选择数据类型 Loading stealth.min.js MySQL config file path: ./mysql_config.json 成功连接到数据库。 Successfully connected to the database. Traceback (most recent call last):…...

大数据Scala教程从入门到精通第一篇:Scala基本介绍

一&#xff1a;Scala基本介绍 1&#xff1a;Scala相当于Java的增强版和拓展 Scala 基于 JVM和 Java 完全兼容。同样具有跨平台、可移植性好、方便的垃圾回收等特性 Scala 比 Java 更加面向对象&#xff0c;可以说完全面对对象。 Scala 是一门函数式编程语言&#xff0c;Java就…...

2-手工sql注入(进阶篇) sqlilabs靶场5-10题

1. 阅读&#xff0c;学习本章前&#xff0c;可以先去看看基础篇&#xff1a;1-手工sql注入(基础篇)-CSDN博客 2. 本章通过对sqlilabs靶场的实战&#xff0c;关于sqlilabs靶场的搭建&#xff1a;Linux搭建靶场-CSDN博客 3. 本章会使用到sqlmap&#xff0c;关于sqlmap的命令&…...

C++并发:线程函数传参(二)

正文 传参中的陷阱 1. 向std::thread 构造函数传参&#xff1a;所有参数&#xff08;含第1个参数可调用对象&#xff09;均按值并以副本的形式保存在 std::thread 对象中的tuple里。这一点的实现类似于std::bind。如果要达到按引用传参的效果&#xff0c;可使用std::ref来传递…...

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-12-蜂鸣器

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…...

H5 css动画效果

你可以使用 CSS 动画来实现这个效果。下面是一个简单的示例代码&#xff0c;展示了如何使用 CSS 中的关键帧动画来放大然后缩小一张图片&#xff0c;并使动画循环播放&#xff1a; html <!DOCTYPE html> <html lang"en"><head><meta charset&qu…...

【大模型】LogRAG:基于检索增强生成的半监督日志异常检测

文章目录 A 论文出处B 背景B.1 背景介绍B.2 问题提出B.3 创新点 C 模型结构D 实验设计D.1 数据集/评估指标D.2 SOTAD.3 实验结果 E 个人总结E.1 优点E.2 不足 A 论文出处 论文题目&#xff1a;LogRAG: Semi-Supervised Log-based Anomaly Detection with Retrieval-Augmented …...

热成像实例分割电力设备数据集(3类,838张)

在现代电力系统的运维管理中&#xff0c;红外热成像已经成为检测设备隐患、预防故障的重要手段。相比传统可见光图像&#xff0c;红外图像可揭示设备温度分布&#xff0c;从而更直观地反映过热、老化等问题。而在AI赋能下&#xff0c;通过实例分割技术对热成像中的电力设备进行…...

Elasticsearch:spring2.x集成elasticsearch8.x

相关安装就不介绍了直接代码集成 <!-- elasticsearch版本需要和你安装的版本一致 --><properties><elasticsearch.version>8.11.1</elasticsearch.version><jakarta-json.version>2.1.2</jakarta-json.version><logstash.version>7…...

亚马逊AWS云服务器高效使用指南:最大限度降低成本的实战策略

对于初次接触云计算的企业或个人开发者而言&#xff0c;亚马逊云服务器&#xff08;Amazon EC2&#xff09;的配置与成本控制往往面临双重挑战&#xff1a;既要理解数百种实例规格的技术参数&#xff0c;又要避免因配置不当导致的资源浪费。本文将深入剖析AWS EC2的核心使用场景…...

Vulkan 3D Tiles渲染器开发笔记1-脚手架搭建

一、项目简介 项目技术栈 CesiumNative + Dear ImGui + Vulkan 1.3 三维地理可视化系统 详细项目功能说明 1. 3DTiles渲染功能 实现完整的3DTiles格式解析与加载引擎支持LOD(Level of Detail)分层细节渲染可加载建筑模型、点云等3DTiles资产示例:加载城市级建筑3DTiles数据…...

【八股消消乐】构建微服务架构体系—服务注册与发现

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本专栏《八股消消乐》旨在记录个人所背的八股文&#xff0c;包括Java/Go开发、Vue开发、系统架构、大模型开发、具身智能、机器学习、深度学习、力扣算法等相关知识点&#xff…...

Paraformer分角色语音识别-中文-通用 FunASR demo测试与训练

文章目录 0 资料1 Paraformer分角色语音识别-中文-通用1 模型下载2 音频识别测试3 FunASR安装 &#xff08;训练用&#xff09;4 训练 0 资料 https://github.com/modelscope/FunASR/blob/main/README_zh.md https://github.com/modelscope/FunASR/blob/main/model_zoo/readm…...

vscode调试deepspeed的方法之一(无需调整脚本)

现在deepspeed的脚本文件是&#xff1a; # 因为使用 RTX 4000 系列显卡时&#xff0c;不支持通过 P2P 或 IB 实现更快的通信宽带&#xff0c;需要设置以下两个环境变量 # 禁用 NCCL 的 P2P 通信&#xff0c;以避免可能出现的兼容性问题 export NCCL_P2P_DISABLE"1" …...

[面试精选] 0104. 二叉树的最大深度

文章目录 1. 题目链接2. 题目描述3. 题目示例4. 解题思路5. 题解代码6. 复杂度分析 1. 题目链接 104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 2. 题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点…...

[ElasticSearch] DSL查询

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...