Linux动态库与静态库解析

news2024/11/25 20:55:11

文章目录

  • 一、引言
  • 二、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.o
	ar -rc $@ $^
	
%.o:%.c
	gcc -c $< 
	
.PHONY:clean
clean:
	rm -rf *.o  mylib *.a

.PHONY:output
output:
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp ./*.h mylib/include
	cp ./*.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.o
	gcc -shared -o $@ $^
%.o:%.c
	gcc -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.o
	gcc -shared -o $@ $^
%.o:%.c
	gcc -c -fPIC $< 
.PHONY:clean
clean:
	rm -rf *.o  libmyc.so mylib 

.PHONY:output
output:
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp -rf *.h mylib/include
	cp -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使用。如果只有静态库,那我们的可执行程序只能进行静态链接,但是程序不一定整体是静态链接的。如果只有动态库,默认只能动态链接,若非要静态链接,会发生链接报错。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1648847.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python小技巧】matplotlib不显示图像竟是numpy惹的祸

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、问题&#xff1a;df.plot() 显示不出图像二、尝试各种解决办法1. 增加matplotlib.use&#xff0c;设定GUI2. 升级matplotlib版本 三、numpy是个重要的库1. …

详解MySQL常用的数据类型

前言 MySQL是一个流行的关系型数据库管理系统&#xff0c;它支持多种数据类型&#xff0c;以满足不同数据处理和存储的需求。理解并正确使用这些数据类型对于提高数据库性能、确保数据完整性和准确性至关重要。本文将详细介绍MySQL中的数据类型&#xff0c;包括数值类型、字符…

算法分析 KMP算法中next值的计算、0/1背包问题

5.6.1 KMP算法中next值的计算 设模式的长度为m。用蛮力法求解 KMP算法中的 next值时&#xff0c;next[0]可直接给出&#xff0c;计算next[j](1<j<m-1)则需要在 T[0] …T[j-1]中分别取长度为j-1、..、2、1的真前缀和真后缀并比较是否相等&#xff0c;最坏情况下的时间代价…

2024------MySQL数据库基础知识点总结

-- 最好的选择不是最明智的&#xff0c;而是最勇敢的&#xff0c;最能体现我们真实意愿的选择。 MySQL数据库基础知识点总结 一、概念 数据库&#xff1a;DataBase&#xff0c;简称DB。按照一定格式存储数据的一些文件的组合顾名思义: 存储数据的仓库&#xff0c;实际上就是一…

Java初识继承

继承 文章目录 继承为什么需要继承继承中变量的访问特点继承中方法的访问特点继承的优缺点 概念:在Java中&#xff0c;继承是面向对象编程的一个基本特性。它允许我们定义一个新类&#xff0c;它从另一个已经存在的类继承其属性和方法。被继承的类称为父类或超类&#xff0c;新…

Linux进程——Linux进程与进程优先级

前言&#xff1a;在上一篇了解完一部分常见的进程状态后&#xff0c;我们先来把剩下的进程状态了解一下&#xff0c;再来进入进程优先级的学习&#xff01; 如果对前面Linux进程不太熟悉可以先阅读&#xff1a; Linux进程 本篇主要内容&#xff1a; 僵尸进程和孤儿进程 Linux进…

63-HDMI转VGA电路设计

视频链接 HDMI转VGA电路设计01_哔哩哔哩_bilibili HDMI转VGA电路设计 HDMI电路设计&#xff08;参考第13课&#xff09; VGA电路设计&#xff08;参考第15课&#xff09; DP转VGA电路设计&#xff08;参考第75课&#xff09; 1、HDMI转VGA转换器 2、HDMI转VGA简介 1、解…

融知财经:期货风险有多大,期货风险进行控制的方法

期货价格变化远大于股票市场&#xff0c;其风险也大于股票市场&#xff0c;当然其预期收益机会相对较高&#xff0c;因此期货更适合激进型投资者。在期货投资市场中&#xff0c;除了市场外的常见风险外&#xff0c;转型期投资者的投机心理和杠杆效应会增加期货的交易风险&#…

获取波形极值与间距并显示

获取并显示波形的极值与极值间距 1、流程 1、通过signal.find_peaks获取极大值 2、获取极大值下标 3、获取极大值对应的值 4、获取极大值的下标间距(就是隔多远有一个极大值) 5、获取极大值间距的标准差、方差、均值、最大值 6、图形展示波形图并标记极大值2、效果图 3、示…

铜价飙升,慧能泰HUSB332F带你狂飙

铜价&#xff0c;近期涨的很飘&#xff0c;涨到怀疑人生。继黄金后&#xff0c;铜成了另一个疯涨的明星&#xff01;作为电线电缆生产不可或缺的原材料&#xff0c;铜的身价暴涨直接拉响了成本警报&#xff0c;压缩了企业的利润空间。众多电线电缆制造商面临着严峻的挑战与考验…

嵌入式学习<1>:建立工程、GPIO

嵌入式学习_part1 本部分笔记用于学习记录&#xff0c;笔记源头 >>b站江科大_STM32入门教程 建立工程、GPIO 开发环境&#xff1a;keil MDK、STM32F103C8T6 1 &#xff09;建立工程 &#xff08;1&#xff09;基于寄存器开发、基于标准库 或者 基于HAL库开发; &…

【Python】字典题

题目&#xff1a;输入一段文本&#xff0c;统计每个字符的个数 in_inputinput(“输入&#xff1a;”) dic{} for char in in_input: if char in dic: dic[char]1 # 字典添加键值对的方法&#xff0c;给字典给键和值的方法 else: dic[char]1 print(dic) for key,value in dic.i…

Elastic 通过 AI 驱动的安全分析改变 SIEM 游戏

作者&#xff1a;Santosh Krishnan, Jennifer Ellard 借助由搜索 AI 提供支持的新攻击发现功能&#xff0c;优先考虑攻击&#xff0c;而不是警报。 传统的安全信息与事件管理系统&#xff08;SIEM&#xff09;在很大程度上依赖屏幕背后的人类才能取得成功。警报、仪表盘、威胁…

DCEP数字人民币:中国法定区块链中数字货币

一、背景 作为全球第二大经济体&#xff0c;中国在数字货币领域的发展一直备受关注。近年来&#xff0c;中国政府积极推动数字货币的研究和试点工作&#xff0c;逐步开放数字货币交易试点&#xff0c;并计划推出中国唯一合法数字货币——数字人民币&#xff08;RMB Coin&#…

tcping的安装,ping和tcping的区别

ping和tcping的区别 功能不同&#xff1a; Ping&#xff1a;Ping是一种基于ICMP协议的网络工具&#xff0c;用于测试主机之间的连通性。它发送ICMP回显请求&#xff08;Echo Request&#xff09;到目标主机&#xff0c;并等待目标主机返回ICMP回显应答&#xff08;Echo Reply…

【Unity】使用Resources.LoadAll读取文件的顺序问题

最近在做客户的一个项目&#xff0c;其中的一个模块使用到了照片&#xff0c;但是发现了一个很严重的问题。当你在使用Unity的时候&#xff0c;它竟然不按照顺序读取&#xff1f;这个机器人是不是逻辑有问题&#xff1f;如下图&#xff1a; 名字脱敏了哈。。。 照片比较多&…

订单超时自动取消的实践方案

1、定时任务方案 方案流程&#xff1a; 每隔 30 秒查询数据库&#xff0c;取出最近的 N 条未支付的订单。 遍历查询出来的订单列表&#xff0c;判断当前时间减去订单的创建时间是否超过了支付超时时间&#xff0c;如果超时则对该订单执行取消操作。 定时任务方案工程实现相…

DAPP开发:揭秘DAPP软件开发的秘密

随着区块链技术的飞速发展&#xff0c;DAPP&#xff08;去中心化应用&#xff09;的开发逐渐成为了一个热门话题。在本文中&#xff0c;我们将探讨如何从零开始开发DAPP软件&#xff0c;并深入思考DAPP开发中的关键问题。 一、了解DAPP开发的基础知识 在开始开发DAPP之前&…

Web3:下一代互联网的科技进化

随着科技的不断演进&#xff0c;互联网已经成为了我们生活中不可或缺的一部分。而在Web3时代&#xff0c;我们将会见证互联网进化的下一个阶段。本文将探讨Web3作为下一代互联网的科技进化&#xff0c;以及它所带来的重要变革和影响。 传统互联网的局限性 传统互联网存在诸多…

绘画作品3d数字云展厅提升大众的艺术鉴赏和欣赏能力

3D虚拟展厅作为未来艺术的展示途径&#xff0c;正逐渐成为文化创意产业蓬勃发展的重要引擎。这一创新形式不仅打破了传统艺术展览的局限性&#xff0c;更以其独特的魅力吸引着全球观众的目光。 3D虚拟艺术品展厅以其独特的魅力&#xff0c;助力提升大众的艺术鉴赏和欣赏能力。观…