📃个人主页:island1314
🔥个人专栏:Linux—登神长阶
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
前言🚀
💢 由于CentOS 8系统2021年12月31日已停止维护服务,CentOS 7系统将于2024年06月30日停止维护服务。CentOS官方不再提供CentOS 9及后续版本,不再支持新的软件和补丁更新。CentOS用户现有业务随时面临宕机和安全风险,并无法确保及时恢复。
因此我把系统换成了 ubuntu ,现在及后面的 Linux 相关博客都会用 ubuntu 来做演示
1. 背景 📑
🔥 在实践中,我们一定会使用别人的库(不限于C、C++的库),在实践中,我们会使用成熟、被广泛使用的第三方库,而不会花费很多时间自己造轮子,为了能更好地使用库,就要在学习阶段了解其本质。那么对于库而言,可以从两方面认识它:
- 如果想自己写一个库呢?-->(编写者)
- 别人应该如何使用我们写的库?-->(使用者)
一个 c 程序生成一个可执行程序(.exe)步骤如下:
- 预处理: 头文件展开、去注释、宏替换、条件编译等,生成.i文件;
- 编译: 词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,生成.s文件。
- 汇编: 将汇编指令转换成二进制指令,生成.o文件。
- 链接: 将生成的各个.o文件进行链接,生成可执行程序(注意:Windows:.exe,Linux:.out)
补充:各类文件后缀含义(了解)
- .o- 目标文件,是源代码编译后生成的二进制文件。链接器在链接阶段将目标文件与其他目标文件和库结合生成可执行文件。
- .a- 静态库文件,由多个目标文件(归档文件)打包而成的。在链接时与其他代码一起静态链接,生成最终的可执行文件。
- .so- 共享对象文件(动态库)。.so是shared object的缩写,表示这个文件是一个共享对象,可以在多个程序之间共享,并且可以在程序运行时被载入,可以在在运行时动态链接,减少内存使用和文件大小
- .i- 预处理后的C或C++源代码文件,在编译前查看预处理结果,包括宏展开和头文件等
- .s- 汇编语言文件。包含编译器将源代码编译为汇编代码后的结果。可以用于手动优化或调试汇编代码。
- .out- 可执行文件,通常用于Linux系统生成的默认可执行文件。在没有指定输出文件名时,生成的可执行文件会默认命名为
a.out
- .exe- 在Windows环境下生成的可执行文件,用户可以直接运行。注意:.exe文件只能在Windows操作系统上运行,而不能在其他操作系统(如Linux或macOS)上运行
💧 当有多个不同的源文件中的main函数调用这些功能函数时,每次都要重新对这几个函数重复预处理、编译、汇编操作,各自生成.o文件,然后再和调用功能函数的源文件(一般是main函数)生成的.o,最后才生成可执行程序
这样会有很多重复的操作,所以一般将这些常用的函数所在的**.cpp文件预处理、编译、汇编生成的多个.o文件打包在一起,称之为库**。而事实上我们经常使用的<stdio.h>、<iostream> 以及使用各种STL容器包含的头文件都是这么做的
由此可以见库的本质:
- 是若干个目标文件(.o文件)的集合。每个.o文件都包含了由源码编译生成的二进制代码,以供调用
严格地说,库并不是可执行程序的半成品
- 库是一组预先编译好的目标文件(.o文件)的集合,它们可以被链接到可执行程序中,以提供某些功能
- 库中的目标文件包含了机器语言代码,但它们并不能直接运行
- 要生成一个可执行程序,需要将库中的目标文件与其他目标文件(如含有main函数的目标文件)链接在一起,然后由链接器生成一个可执行文件
2. 初识动静态库 🔍
先来看一段代码
#include <stdio.h>
#include <string.h>
int main()
{
char buffer[1024];
strcpy(buffer, "Hello Island1314");
printf("%s\n", buffer);
return 0;
}
上图中,我们只是调用了接口,并没有去实现该函数。
但是可执行程序执行必须要有对应的实现,所以编译时,由gcc默认帮我们链接了对应的库。
- ldd 的作用是帮我们找可执行程序所依赖的库
- ldd是一个命令,它用于打印程序或库文件所依赖的共享库列表。它不是一个可执行程序,而只是一个 shell 脚本
其中,libc.so.6就是这个可执行程序依赖的库文件,通过**
ll
**指令查看这个该路径下这个库文件的属性:
表明它其实是软链接到同目录下的 **libc.so.6 文件,通过
file
**指令,查看该文件的文件类型:
🎐如果一个库文件是symbolic link,那么它是一种特殊的目标文件,可以在程序运行时被加载(链接)进来。
去掉前缀和后缀,剩下的就是库的名字,即 libc.so.6实际上是C语言的动态库,库名是 c
- libc.so.6 是 glibc 的软链接。glibc 是 GNU 发布的 libc 库,即 C 运行库
如果我们要生成静态链接如下:
gcc code.c -o code -static
- 关于Linux的动静态库问题
- 在Windows下,原理是一样的。
- 默认形成可执行程序:动态链接
- Windows下动态库后缀为**.dll,静态库后缀为.lib**
注意:系统本身为了支持编程,除了提供标准库.h,还提供了标准库的实现**.so / .a**
**3. 动静态库基本概念 📚 **
3.1 基本定义及特点
静态库
1. 定义
- 静态库是编译后生成的一组目标文件的集合,通常以****.a ****为后缀(如 ****libmylib.a ****)
- 在编译时,这些目标文件的代码会被直接复制到最终生成的可执行文件中
- 程序运行的时候将不再需要静态库
2. 特点
- 打包完整:所有的库代码在编译时被包含到可执行文件中。
- 链接方式:链接在编译时完成,静态库的函数和变量会成为最终可执行文件的一部分
动态库
1. 定义
动态库是编译后生成的共享对象文件,通常以 .so为后缀(如 libmylib.so)。这些库在程序运行时动态加载,多个程序可以共享同一个动态库。
2. 特点
- 运行时链接:程序在运行时需要动态链接库,函数和变量在执行时才会被加载。
- 共享机制:动态库可以被多个程序共享使用,减少内存消耗。
3.2 优缺点
静态库
优点
- 独立性:生成的可执行文件不依赖于外部的库文件,能够在没有该库的环境中运行,由于静态编译的时候,已经将静态库中的代码都放到可执行程序的正文代码中了,所以可执行程序本身是一个独立的文件
- 运行效率:由于不需要在运行时加载库,程序的启动时间较短。
缺点
- 文件体积:可执行文件体积较大,因为它包含了所有的库代码。
- 更新复杂:如果库更新,所有依赖于该库的可执行文件都需要重新编译。
动态库
优点
- 文件体积小:可执行文件相对较小,因为不包含库的所有代码。
- 易于更新:只需替换动态库文件即可更新,而不需要重新编译依赖该库的所有程序。
缺点
- 运行时依赖:如果缺少所需的动态库,程序无法运行。
- 加载时间:在启动时需要加载库,可能会稍微增加启动时间。
3.3 使用场景
静态库
- 适用于不需要频繁更新库的场景。
- 对于嵌入式系统或需要快速启动的应用,静态库也是一个不错的选择。
动态库
- 适合需要频繁更新和维护的应用程序。
- 多个应用共享相同库时,动态库能有效节省资源。
4. 动静态库的制作和使用 🖊
4.1 库的连接
在学习制作库之前,我们先来看看 库的链接
下面是一个简单的示例,展示了如何编写一个 add 函数,并且如何将它们编译成目标文件(.o 文件),最后将这些目标文件链接起来形成一个可执行文件。
首先,我们需要创建 2 个源文件:
- Add.h - 包含Add 函数定义的头文件
- Add.c - 包含 Add 函数的实现
- main.c - 包含 main 函数,用于调用 add 函数
// Add.h
#pragma once // 防止头文件重复包含
#include <stdio.h>
int add(int x, int y);
// Add.c
#include "Add.h"
int add(int x, int y)
{
return x + y;
}
// Main.c
#include <stdio.h>
extern int add(int a, int b);
int main()
{
int a = 10, b = 20;
printf("Add: %d + %d = %d\n",a, b, add(a, b));
return 0;
}
**extern **告诉编译器 add 函数存在,但它的定义(函数体)在其他地方
现在,我们将这些源文件编译成目标文件,并链接它们来创建一个可执行文件。
编译和链接步骤
在 Linux 命令行中执行以下命令:
这样,我们就测试了 Linux 下的 GCC 编译器如何将
.o
文件链接起来形成可执行文件。
🦋 我们在给被人静态库的时候,需要把 .a/.so 库文件和 .h 头文件给到对方。.a/.so 让别人可以调用库中的函数,.h 告诉别人库中有哪些函数可以被调用,相当于函数使用的说明书
- 在使用第三方库的时候,需要指明库的名称
下面我们做库需要用到的文件如下:
Main.c 文件
#include <my_stdio.h> // <> 系统默认路径
#include <my_string.h>
// #include "my_stdio.h" // " " 指 头文件在当前路径下
// #include "my_string.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
mFILE *fp = mfopen("./log.txt", "a");
if(fp == NULL)
{
return 1;
}
int cnt = 3;
while(cnt)
{
printf("write: %d\n", cnt);
char buffer[64];
snprintf(buffer, sizeof(buffer), "hello message, number is : %d", cnt);
cnt--;
mfwrite(buffer, strlen(buffer), fp);
mfflush(fp);
sleep(1);
}
mfclose(fp);
}
my_stdio 系列文件
// my_stdio.h
#pragma once
#define SIZE 1024
#define FLUSH_NONE 0
#define FLUSH_LINE 1
#define FLUSH_FULL 2
struct IO_FILE
{
int flag; // 刷新方式
int fileno; // 文件描述符
char outbuffer[SIZE];
// 缓冲区
int cap; // 容量
int size; // 大小
// TODO
};
typedef struct IO_FILE mFILE;
mFILE *mfopen(const char *filename, const char*mode); // 打开文件
int mfwrite(const void *ptr, int num, mFILE *stream); // 写入数据
void mfflush(mFILE *stream);
void mfclose(mFILE *stream);
// my_stdio.c
#include "my_stdio.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
mFILE *mfopen(const char *filename, const char*mode)
{
int fd = -1;
if(strcmp(mode, "r") == 0)
{
fd = open(filename, O_RDONLY);
}
else if(strcmp(mode, "w") == 0)
{
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
}
else if(strcmp(mode, "a") == 0)
{
fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
}
if(fd < 0) return NULL;
mFILE *mf = (mFILE*)malloc (sizeof(mFILE));
if(!mf)
{
close(fd);
return NULL;
}
mf->fileno = fd;
mf->flag = FLUSH_LINE;
mf->size = 0;
mf->cap = SIZE;
return mf;
}
void mfflush(mFILE *stream)
{
if(stream->size > 0) // 缓冲区里面有内容
{
// 写到内核文件的文件缓冲区中!!
write(stream->fileno, stream->outbuffer, stream->size);
// 刷新到外设
fsync(stream->fileno);
stream->size = 0;
}
}
int mfwrite(const void *ptr, int num, mFILE *stream)
{
// 1. 拷贝
memcpy(stream->outbuffer + stream->size, ptr, num);
stream->size += num;
// 2. 检测缓冲区是否要刷新
if(stream->flag == FLUSH_LINE && stream->size > 0 && stream->outbuffer[stream->size - 1] == '\n')
{
mfflush(stream);
}
return num;
}
void mfclose(mFILE *stream)
{
// 刷新之前,先判断缓冲区是否有内容
// 将内容刷新到缓冲区
if(stream->size > 0)
{
mfflush(stream);
}
close(stream->fileno); // 进行文件刷新
}
my_string 系列文件
// my_string.h
#pragma once
int my_strlen(const char *s);
// my_string.c
#include "my_string.h"
int my_strlen(const char *s)
{
const char *end = s;
while(*end != '\0') end++;
return end - s;
}
Makefile 文件
# 生成静态库
libmystdio.a: my_stdio.o my_string.o
@ar -rc $@ $^
@echo "build $^ to $@ ... done"
%.o:%.c
@gcc -c $<
@echo "compling $< to $@ ... done"
.PHONY: clean # 清除
clean:
@rm -rf *.a *.o stdc*
@echo "clean ... done"
.PHONY: output # 发布静态库
output:
@mkdir -p stdc/include
@mkdir -p stdc/lib
@cp -f *.h stdc/include
@cp -f *.a stdc/lib
@tar -czf stdc.tgz stdc # 打包
@echo "output stdc ... done"
# 生成动态库
libmystdio.so: my_stdio.o my_string.o
gcc -o $@ $^ -shared
%.o:%.c
gcc -fPIC -c $<
.PHONY: clean
clean:
@rm -rf *.so *.o stdc*
@echo "clean ... done"
.PHONY: output # 发布静态库
output:
@mkdir -p stdc/include
@mkdir -p stdc/lib
@cp -f *.h stdc/include
@cp -f *.so stdc/lib
@tar -czf stdc.tgz stdc # 打包
@echo "output stdc ... done"
4.2 静态库
4.2.1 制作
- 编译同名的目标文件
gcc -c my_stdio.cgcc -c my_string.c
- 生成静态库
- 要创建库文件——实际上是一个存档文件——我们将使用
ar
,使用-c
(创建)选项创建库文件,使用-r
(添加替换)选项将文件添加到库文件,以及-s
(索引)选项,用于在库文件中创建文件索引
ar -rc libmystdio.a my_stdio.o my_string.o
注意:
- mystdio 静态库前后需要加上lib和.a,使用的时候需省略
4.2.2 使用
🍉 a. 安装到系统里面
sudo cp *.h /usr/include/
sudo cp libmystdio.a /lib64/
- 系统库目录:***/usr/lib64 或 /usr/lib***
- 系统头文件目录:***/usr/include***
** 查询指定目录结果如下:**
注意:即使我们将头文件和库文件拷贝到系统目录下,但是 gcc 在编译main.c时,还需要显式地说明要链接的库在哪一路径下。
通过****-l 命令选项让 gcc 知道这是一个第三方库,而库名就是llibmysdtio.a去掉 前缀lib 和 后缀.a 剩下的部分,即lmysdtio****
gcc -main.c -lmystdio
原因:这是因为gcc在默认路径下是链接C/C++的库,它只知道哪些是内置的库,而不知道第三方库的存在
- 注意:这里的 main.c 的头文件用 <> ,表示在系统路径下
删去我们之前安装到系统的路径
sudo rm /usr/include/my_*
sudo rm /lib64/libmystdio.a
现在我们再去指定目录下查找:(发现其就就不在了)
注意:但是将头文件和库文件添加到系统目录下是非常不推荐的,因为这样会污染系统库目录,而这就是安装库的过程
🍉 b. 手动调用静态库
调用的函数的库文件在哪
- 使用 -L + 库文件的路径(告诉编译器,在我指明的路径下找)
调用的库的名称
- 使用 -l + 库的名称
gcc main.c -o main -L. -lmystdio # 当前路径
注意:此时使用的时候,头文件以及库其实都处于当前路径下了
🍉 c. 使用带路径的库
调用上面的 Makefile 文件(静态库)
.PHONY:output # 发布静态库
output:
@mkdir -p stdc/include
@mkdir -p stdc/lib
@cp -f *.h stdc/include
@cp -f *.a stdc/lib
@tar -czf stdc.tgz stdc
@echo "output stdc ... done"
- 此时通过 Makefile 文件,使用 make ouput 将头文件和库文件分门别类放在 stdc 的目录下
gcc 编译 main.c 链接库时,需要由以下三个选项定位文件:
-I
:指定头文件搜索路径。-L
:指定库文件搜索路径。-l
:指明需要链接库文件路径下的哪一个库。
先把之前做好的stdc 目录打包复制到当前 main.c 的目录下,然后解包运行程序
gcc main.c -Istdc/include -Lstdc/lib -lmystdio
4.3 动态库
4.3.1 制作
依然使用之前的四个文件和一个main.c文件示例。
- 生成所有源文件对应的目标文件
gcc 需要增加**-fPIC选项(position independent code):位置无关码**
gcc -fPIC -c my_stdio.c
gcc -fPIC -c my_string.c
- 使用 gcc 的 -shared 选项将所有目标文件打包为一个动态库
gcc -o libmystdio.so my_stdio.o my_string.o -shared
位置无关码(了解)
🎈 位置无关代码(Position Independent Code,PIC)是一种特殊的机器代码,它可以在内存中的任何位置运行,而不需要重新定位。这意味着,当程序被加载到内存中时,它的代码段可以被放置在任何可用的内存地址,无需修改代码中的任何地址引用
- 这对于创建共享库(即动态库)非常有用,因为共享库可以被多个程序同时使用,而每个程序都可能将其加载到不同的内存地址。
- 如果共享库中的代码不是位置无关的,那么每次加载时都需要对其进行重新定位,这会增加程序启动的时间和内存占用
而使用位置无关代码可以避免这些问题
- 因为它可以在内存中的任何位置运行,而不需要重新定位。
- 这样,当多个程序使用同一个共享库时,它们都可以直接使用共享库中的代码,而不需要对其进行重新定位。
- 这样可以节省大量的 RAM,因为共享库的代码节只需加载到内存一次,然后映射到许多进程的虚拟内存中。
和静态库采用的绝对编址相比,动态库采用的就是相对编址,各个模块在库中的地址可能不相同,但是它们之间的相对位置是固定的。就好像房车旅行一样,房车的位置虽然一直在变,但是房车内家具的相对位置一直不变。
位置无关代码对于 gcc 来说:
- -fPIC 作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的
- 如果不加 -fPIC 选项,则加载 **.so **文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 .so 文件代码段的进程在内核里都会生成这个 .so 文件代码段的拷贝,并且每个拷贝都不一样,这样就和动态库一样占用内存了,具体取决于这个 **.so **文件代码段和数据段内存映射的位置
- 不加**-fPIC** 编译生成的 .so 文件是要在加载时根据加载到的位置再次重定位的,因为它里面的代码 BBS 位置无关代码。如果该 .so 文件被多个应用程序共同使用,那么它们必须每个程序维护一份 **.so **的代码副本 (因为 .so 被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
- 我们总是用**-fPIC** 来生成 .so,但从来不用 **-fPIC **来生成. a。但是 **.so **一样可以不用 -fPIC 选项进行编译,只是这样的 .so 必须要在加载到用户程序的地址空间时重定向所有表目
4.3.2 使用
🍒 a. 安装到系统里面
sudo cp *.h /usr/include/
sudo cp libmystdio.so /lib/
和静态库那里类似,使用 -l 命令告诉 gcc 我们使用的库是什么即可
gcc main.c -lmystdio
注意:****Ubuntu 自身系统设定的相应的设置的原因,即其只在 /lib和 /usr/lib 下搜索对应 的.so 文件,故需将对应 .so 文件拷贝到对应路径。 Centos则是 /lib/64
🍒 b. 手动调用静态库
当我们有了头文件和库文件的时候不安装到系统里,和静态库类似
🍒** c. 使用带路径的库**
.PHONY:output #动态库
output:
@mkdir -p stdc/include
@mkdir -p stdc/lib
@cp -f *.h stdc/include
@cp -f *.so stdc/lib
@tar -czf stdc.tgz stdc
@echo "output stdc ... done"
- 此时通过 Makefile 文件,使用 make ouput 将头文件和库文件分门别类放在 stdc 的目录下
gcc 编译 main.c 链接库时,需要由以下三个选项定位文件:
-I
:指定头文件搜索路径。-L
:指定库文件搜索路径。-l
:指明需要链接库文件路径下的哪一个库。
先把之前做好的 stdc 目录打包复制到当前 main.c 的目录下,然后解包运行程序
gcc main.c -Istdc/include -Lstdc/lib -lmystdio
当我们对文件进行运行的时候,却发现:
- 不同于静态库,这里动态库生成的可执行程序并不能运行
- 发现libmystdio.so => not found,这说明系统无法找到动态库文件。
在Linux下,有以下几种使用第三方动态库的方法(解决以上问题):
(1)拷贝到系统默认路径
sudo cp libmystdio.so /lib/
在编译链接时,只需要记录需要链接文件的编号,运行程序时才会进行真正的“链接”,所以称为“动态链接”。因此,只要将动态库的.so文件拷贝到系统目录下,这个可执行程序就可以被链接到动态库,而不需要重新编译链接。
也就是说,编译器只负责生成一个main.c对应的二进制编码文件,而链接的工作要等到运行程序时才会进行链接,所以生成可执行程序以后就没有编译器的事了。
- 缺点:同样地,将动态库的.so文件拷贝到系统目录下也可能会污染系统库目录
- 注意:由于我们用的是 ubuntu 系统,因此不是 /lib/64 ,而是 /lib
(2)在 /lib/ 下建立软链接
sudo ln -s ~/112/lesson22/stdio/libmystdio.so /lib/libmystdio.so
(3)设置LD_LIBRARY_PATH环境变量
LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径,我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量中,告诉系统程序依赖的动态库所在的路径:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/lighthouse/112/lesson22/other/stdc/lib
** 注意:**要用
:
隔开,否则会覆盖原来的环境变量。但是这个方法是临时的,因为这个环境变量是内存级别的环境变量,机器会在下次登录时清理。
- 在 ~/ 下 有一个 .bashrc 隐藏文件,我们只需要将命令添加进来即可永久保存
(4)使用ldconfig指令
🎈 /etc/ld.so.conf.d/ 目录下的文件用来指定动态库搜索路径。这些文件被包含在/etc/ld.so.conf. 文件中,ldconfig 命令会在默认搜寻目录 (/lib 和 /usr/lib)以及动态库配置文件**/etc/ld.so.conf.**内所列的目录下,搜索可共享的动态链接库,并创建出动态装入程序(ld.so)所需的连接和缓存文件
这些**.conf 文件中存储的都是各种文件的路径,只要将我们写的第三方库的路径保存在.conf **文件中,程序运行时在就会通过它链接到它依赖的动态库
(1)将路径存放在**.conf **文件中
echo /home/xy/Linux/libtest/libtest/mylib/lib > libtest.conf
(2)将**.conf 文件拷贝到/etc/ld.so.conf.d/ **下
sudo cp libmystdio.conf /etc/ld.so.conf.d/
ldd一下:
系统还是没有找到a.out依赖的动态库,原因是此时的系统的数据库还未更新,使用命令ldconfig更新配置文件:
sudo ldconfig
5. 动态库小知识
5.1 默认优先链接动态库
🎈 如果同时存在动态库和静态库文件,gcc会优先链接动态库。如果你想强制gcc链接静态库,可以直接指定静态库的全称,或者使用 **-static **选项。你也可以使用 **-Bdynamic 和-Bstatic **选项在命令行中切换首选项。
- 如果系统中只提供.so文件,则只能使用动态链接
- 如果只提供.a文件,即使不指定-static,也是静态链接
- 如果动态库和静态库都存在,默认情况下,gcc/g++ 采用动态链接的方式链接第三方库,但是可以使用 -static 参数改变默认链接方式。
5.2 动态库分批加载到内存
🎈 动态库在程序运行时才被加载,它和可执行文件是分开的,只是可执行文件在运行的某个时期调用了它。动态库可以实现进程之间资源共享,有一份就行
💦可执行程序先加载,代码区的代码运行到动态库的方法时,可以先跳转到共享区,而共享区是保存了页表和动态库的映射关系的,所以使用完动态库的方法以后,再返回到代码区继续执行即可。由此可见,静态库是在可执行程序自己的进程地址空间中跳转的。
5.3 动态库的意义
👁 动态库把对一些库函数的链接载入推迟到程序运行的时期,可以实现进程之间的资源共享,将一些程序升级变得简单。
- 库是别人写好的现有的、成熟的、可以复用的代码,用户可以按照说明使用库。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
- 可以减少代码冗余,只需要一份即可。分批加载后,只要建立相对地址关系即可。如果有很多个进程都要用同一个库,只要建立映射关系即可。只要加载一次,就可以在内存中被多份代码复用。继续执行即可。由此可见,静态库是在可执行程序自己的进程地址空间中跳转的
6. 小结 📖
- 静态库链接的时候把库直接加载到程序中(也就是直接拷贝一整份库)
- 而动态库链接的时候,它只是保留接口,将动态库与程序代码独立。这样就可以提高代码的可复用度和降低程序的耦合度。动态库相对于静态库有便于更新拓展、减小体积等诸多优势。
但这不意味着静态库是一无是处的
🔥 当你更希望简单易用,尽量免除后顾之忧,那就首选静态(隐式)连接。静态库在链接时就和可执行文件在一块了,因此,对于同样的程序,静态链接的要比动态链接加载更快。所以选择静态库还是动态库是空间和时间的考量。但是通常来说,牺牲这点性能来换取程序在空间上的节省和部署的灵活性时值得的。
【★,°*:.☆( ̄▽ ̄)/$:*.°★ 】那么本篇到此就结束啦,如果我的这篇博客可以给你提供有益的参考和启示,可以三连支持一下 !!**
版权归原作者 IsLand1314~ 所有, 如有侵权,请联系我们删除。