动态库与静态库

动态库/静态库的概念

静态库(.a后缀结尾):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静 态库

动态库(.so后缀结尾):程序在==运行的时候才去链接==动态库的代码,多个程序共享使用库的代码。

本质来说静态库就是==将目标代码拷贝到可执行程序里面!==

动态库就是不拷贝代码,==只拷贝所需要的库函数的地址==

例如我们使用printf,静态库就是将c标准库里面的printf的实现代码都拷贝进来!

动态库就是将c标准库里面的printf在库中的地址拷贝进来

一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码

在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个 过程称为动态链接(dynamic linking)

动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚 拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

库文件的本质是什么?

image-20230724100030807

==一般来说如果我们正常的使用这个两个函数是通过包含头文件来实现的==

image-20230724101711006

image-20230724101829053

==对于一个源文件,来说形成可执行文件有四个步骤==

预编译(.i)——编译(.s)——汇编(.o)——链接

==其中在第三步汇编完成后形成的.o文件这是一个二进制文件(可重定位目标二进制文件),是无法执行的!==

但是实际上形成可执行文件并不用从源代码直接开始编译!——也可以从目标二进制文件开始!

image-20230724104208967

我们可以发现可以手动的链接起来形成可执行文件!

我们要明白两种编译方式没有什么区别!上面的使用.c其实也是要经过链接的,只不过我们是首先将三个文件都手动的独自编译后吗,又手动的链接了

我们现在模拟一个场景

image-20230724110311571

==上面这个例子,我们可以得到一个简单的道理——如果我们不想给别人我们的源码,我们可以给对方.o可重定位目标二进制文件,让使用者用自己的代码进行链接==

我们可以给对方提供.o(方法的实现),和.h(有什么方法)

==但是上面的我们只有两个源文件,只形成了两个.o文件,如果我们有10000个源文件,那么形成10000个.o文件,那么使用者是不是太麻烦了?==

所以为了方便使用者使用!——我们能不呢将所有的“.o”文件都进行打包呢?只给对方提供一包呢?——==这个包我们就称之为库文件!==

库文件——多个.o文件合并为一个文件

==而打包文件与打包方式的不同就有了动态库和静态库的区分!==

==所以库文件的本质就是——.o文件的集合!==

如何生成静态库

我们可以使用==ar(archive)命令==来进行打包!

ar -rc xxx(生成文件的名字) xxx.o....(需要的各种.o文件)

rc表示replace and creat

image-20230724113635552

我们可以看到这就形成了一个静态库文件!

image-20230724114003678

可以使用makefile文件一键生成

==那么该如何使用这个库文件呢?==

image-20230724115041516

image-20230724115725892

==我们可以看到我们成功的发布出来了我们的库==

我们可以使用tar来对我们的发布出来的库进行打包

image-20230724193053618

我们就可以将我们打包好的文件发布出来(然后我们就可以放在某个网站)

如何使用发出来的库

那么如何使用我们打包后的文件呢?

我们首先要明白安装的本质就是拷贝我们写的文件到系统指定的能找到的文件下

image-20230724194154014

image-20230724194215639

我们发现即使我们已经解压好了,但是还是没有办法正常的编译,gcc在搜索头文件的时候,只会去系统指定的路径,或者当前路径下,所以还是没有办法

==但是我们可以指定gcc的搜素路径!==

我们可以使用gcc -o xxx xxx.c -I .../.../(I,可以看成是include的单词的首字母)

image-20230724194958874

同样的指定gcc搜索库文件的路径

gcc -o xxx xxx.c -L .../.../(L,可以看成是lib的首字符)

image-20230724195204975

==为什么还是失败了?==

如果我们想要链接库!我们必须==指定库的名称!==——我们必须告诉编译器我们要的是这个路径下的那一个库!(即使只有一个库!)

我们最开始写代码的时候,为什么从来不用指明库名称呢,因为gcc/g++是专门用来编译c/c++的,我们使用的也就是标准库,gcc/g++是可以分清楚我们链接的是那一个库,所以默认给我们填上了,但是==第三方库就要指定!==

我们要使用

gcc -l xxxx来指定我们的库名称!

image-20230724195937619

怎么又报错了?——==因为我们的库名称错误了!库的名字是文件名要去掉前缀,去掉后缀后才是库名字!==

image-20230724200134699

image-20230724200254507

这样子我们就成功的编译了!且能成功运行了!

==我们查看一下这个可执行文件的属性==

image-20230725092240168

我们发现了一个很神奇的地方——我们所罗列出来的依赖库没有我们的自己的库(显示出来的全是动态库

而且写的也都是==动态链接!==

我们使用的难道不是静态库么?

我们要明白,形成一个可执行程序,可能不仅仅依赖一个库!

例如:如果依赖了100个库,其中有70个动态库,30个静态库一那么该怎么链接呢?

我们首先要明白一点gcc默认是动态链接的!(但是这是一个建议行为不强制)

==对于指定的特定的一个库,究竟是静态还是动态,取决于我们提供的库是静态的还是动态的==

我们说的建议是指,当同时存在两个==同名的库==(指去掉前缀和后缀),一个静态一个动态,那么gcc就会选择动态!

无论是是多少个库——gcc也是要一个个的链接的!所以也是依旧要遵循上面的规则!

所以对于70个动态库——那么就是动态链接!对于30个静态库——就是全是静态链接!

==而只要你使用了一个动态库!从整体上来看我们的软件就是动态链接的!==

这就是为什么我们查看我们的可执行文件的时候——它显示的是动态链接,因为我们使用了c的标准库!

==但是有没有觉得我们这样子写麻烦——每次使用一个第三方库都要写怎么长的一堆!==

image-20230725093405829

所以我们可以选择按照在系统路径下——那怎么安装?上面说过安装的本质就是拷贝我们写的文件到系统指定的能找到的文件下,所以我们直接拷贝就行了

image-20230725093949320

上面的这种行为就是安装!

image-20230725094143544

我们再次运行发现为什么还是报错了?——==但是我们发现了这次在我们什么都没有指定的情况下,没有头文件报错,而是链接报错!==

==实际上也不是找不到库!而是gcc不清楚我们要连接哪一个库!——因为lib64下有很多的库,这仅仅只能告诉gcc,你要的库的位置!gcc只认识c/c++的标准库!第三方库是不认识的!——所以我们必须告诉gcc我们的库的名字!==

但是这一次,我们既不用指定头文件路径,也不用指定库路径方便了很多!

image-20230725094618296

这样子就成了!

image-20230725094926525

我们把库从系统路径下删除这个过程就是卸载!

如何生成动态库

如果我们想要形成一个动态库也是一样将所有的.c文件变成.o就可以了!

但是==生成.o的选项又不一样!==

gcc -c  -fPIC xxxx.c

fPIC这个选项的意思就是生成==位置无关码==

image-20230725100440588

==然后打包一下就可以了——但是打包的流程也是不一样——我们使用的是gcc==

gcc -shard -o xxxx(库名称) xxx.o xxxx.o....

image-20230725100724708

==动态库的命名格式和——就是libxxxx.so==(和静态库类似中间的就是库名)

shared: 表示生成共享库格式

image-20230725100842253

==从文件属性我们可以看出来就是一个动态链接库==

然后对库文件和头文件进行打包——方便他人使用

image-20230725102514861

如何使用动态库呢

image-20230725103423739

==我们按照静态库的方式来编译,发现虽然生产了可执行文件,但是最后执行可执行文件的时候却出错了!==

image-20230725103653562

我们可以看到我们的可执行文件确实是一个动态链接的!,而且使用ldd指令,我们甚至可以看到我们自己写的动态库!——==但是为什么找不到?我们不是已经指定好了路径了嘛?而且也说明了库名称了吗?==

我们首先要明白,我们是是将路径和名称告诉的是gcc,但我们编译完成程序后和gcc就已经没有关系了!,==运行的时候操作系统和shell并不知道库在哪里!==——静态库只有编译的时候需要知道库在哪里,但是动态库编译和运行的时候都要知道库在哪里!

一般操作系统和shell回去系统路径下面(lib64)去找,但是我们的库没有在系统路径里面!

==所以我们该怎么告诉操作系统库在哪里呢?==

一般来说除了在系统路径下,操作系统和shell还会去==环境变量==

LD_LIBRARY_PATH

LD——就是load(加载)的缩写

image-20230725114548760

==我们可以将我们的库路径添加进这个环境变量里面!——使用export指令==

image-20230725145838987

==这样子我们成功的将我们的库路径加入到环境变量里面了!——我们也可以用echo指令查找到了!==

image-20230725150006874

image-20230725150206628

ldd指令已经可以找到我们的库的路径了,程序也可以成功的运行了!

但是这种方法有一个文件,我们自己定义的环境变量只在我们自己登录的时候有效,下次重新登录的时候就必须重新设置!

想要解决这个问题有两种方法

第一种就是修改系统的配置文件

image-20230725150804707

在这个路径下有很多的配置文件!

然后我们可以怎么做?——==将我们的动态库的绝对路径写到任意名称的配置文件中去就可以!(你也可以自己传建一个)==

image-20230725151505107

最后很关键的一步

image-20230725151825082

==更新,将我们的配置文件都重新加载==——否则还是会找不到!

image-20230725152056044

这样子我们就可以找到了,而且程序也能使用了!——这次设置就永久生效!

那有没有更加简单有效的方法呢?——有的!

第二种就是当当前工作路径下==创建软链接==

image-20230725152848567

==这样子我们就可以直接的运行使用了!==

或者我们也可以在系统路径下创建软连接这也是可以使用的!

image-20230725153320606

剩下还有最后一种——就是安装(拷贝到系统路径下即可)

==所以我们总共有4总做法1.就是修改环境变量 2.修改系统配置文件 3.就是在当前工作路径/系统路径下创建相应的软链接 4.就是安装到系统路径下==

如果想要试一下可以去尝试安装ncurses库来体验一下!

动静态库的加载过程

静态库的在运行的时候是不用加载的!——所以我们一般也不讨论静态库的加载

因为静态库的代码直接拷贝进我们的代码里面

image-20230725161636744

==当我们使用静态库的时候,静态库会将我们使用的代码拷贝进我们的代码里面,这样子程序在内存运行的时候,就不用依赖库了!==

那么静态库的代码是拷贝到哪去呢?——会被放进代码段里面!我们的程序在编译的时候,会形成自己的进程地址空间!静态库的代码这时候就被编好了关于其指定的虚拟地址,在运行的时候必须通过想对确定的地址(编译是形成的 :进程地址空间的代码区的地址)来继续访问!

动态库的加载

image-20230725172501489

<img src="https://s2.loli.net/2023/07/25/w4gDxmaSlATVEhH.png" alt="image-20230725174440972" />

这就是动态库加载的全过程!——为什么要怎么麻烦不能直接一开始就决定这个库的虚拟地址吗?——不能!因为这个例子中只有一个库!如果有很多个库呢?我们怎么能知道那个库先加载,那个后加载?