动态库和静态库

参考文档:
Linux Basics: Static Libraries vs. Dynamic Libraries
参考视频:
什么是静态库、动态链接库(共享库)
手把手教你linux下面静态库和动态库的制作

库的作用:实现代码的解耦、代码的重用、对外提供服务(export)

Libraries play their role at run time or compile time. 库在运行时或编译时发挥其作用。

Libraries have object files created by the “-c” gcc flag and end in “.o” by convention. They are the result of the output of the compiler and contain function definitions in binary form.
库具有由创建 “-c” gcc flag 的目标文件,并 “.o” 按约定结束。它们是编译器输出的结果,包含二进制形式的函数定义。

动态库具有“.so”命名约定,静态库具有“.a”。

动态库或共享库作为可执行文件之外的单独文件出现。
因此,它在运行时只需要库文件的一个副本。
在编译时,静态库被锁定在程序中。它包含文件的程序在编译时保存库文件的副本。

典型应用:exe调用.dll。打开大型软件的目录里面会有很多.dll文件,少量的.exe。
小软件会把所有的功能写在exe中,大软件会把代码解藕出来因为其中的很多代码是要重用的。

库的分类

库分为静态库和动态链接库(共享库)。
静态库:与exe打包成exe(一个文件)
windows就是.lib文件
linux就是.a文件

动态链接库(共享库):与exe是独立的文件。
windows就是.dll文件。
linux就是.so文件。

衍生技术
黑客 hook dll,代码替换或者代码劫持

关于Linux的动态库和静态库更详细的解释

When using a dynamic library, the programmer is referencing that library when it needs to at runtime. For instance, to access the string length function from the standard input/output header file — you can access it dynamically. It will find the program’s library reference at runtime because of the dynamic loader. It then loads that string length function into memory. Thus, the dynamic library accessibility must be readily available or it becomes powerless.
使用动态库时,程序员在运行时需要引用该库。例如,要从 standard input/output header file — 访问字符串长度函数,您可以动态访问它。由于动态加载器,它将在运行时找到程序的库引用。然后,它将该字符串长度函数加载到内存中。因此,动态库的可访问性必须随时可用,否则它将变得无能为力。


Advantages and Disadvantages of Dynamic Libraries
动态库的优缺点
1.It only needs one copy at runtime. It is dependent on the application and the library being closely available to each other.
它在运行时只需要一个副本。它依赖于应用程序和库彼此紧密可用。

2.Multiple running applications use the same library without the need of each file having its own copy.
多个正在运行的应用程序使用相同的库,而不需要每个文件都有自己的副本。

3.However, what if the dynamic library becomes corrupt? The executable file may not work because it lives outside of the executable and is vulnerable to breaking.
但是,如果动态库损坏怎么办?可执行文件可能无法工作,因为它位于可执行文件之外,并且容易被破坏。

4.They hold smaller files. 它们保存较小的文件。

5.Dynamic libraries are linked at run-time. It does not require recompilation and relinking when the programmer makes a change.
动态库在运行时链接。当程序员进行更改时,它不需要重新编译和重新链接。


At compile time, applications utilize static libraries. All the copies of the functions get placed into the application file because they are needed to run the process.
在编译时,应用程序使用静态库。函数的所有副本都放入应用程序文件中,因为运行进程需要它们。

Advantages and Disadvantages of Static Libraries
静态库的优缺点

1.Static libraries resist vulnerability because it lives inside the executable file.
静态库可以抵抗漏洞,因为它位于可执行文件中。

2.The speed at run-time occurs faster because its object code (binary) is in the executable file. Thus, calls made to the functions get executed quicker. Remember, the dynamic library lives outside of the executable, so calls would be made from the outside of the executable.
运行时的速度更快,因为它的目标代码(二进制)位于可执行文件中。因此,对函数的调用执行速度更快。请记住,动态库位于可执行文件之外,因此将从可执行文件的外部进行调用。

3.Changes made to the files and program require relinking and recompilation.
对文件和程序所做的更改需要重新链接和重新编译。

4.File size is much larger.
文件大小要大得多。

如何创建静态和动态库?

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


.c->.so所有的动态链接库
.o目标文件->.a静态库文件
目标文件:存放目标代码的计算机文件。
静态库文件:指要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。

gcc -c add.c    //把add.c文件编译成add.o文件
gcc -c sub.c    //把sub.c文件编译成sub.o文件
ar rc liball.a dog.o cat.o bird.o   //
//ar创建静态库
//把所有的.o文件合并命名为liball.a的静态库文件,所有的库文件命名都必须以lib为开始
//指定这里要合并的目标文件为dog.o cat.o bird.o

创建静态文件的过程
在这里插入图片描述
生成静态库之后,需要新建一个头文件,填写如下的内容:
在这里插入图片描述
在这里插入图片描述
写一个测试的demo.c文件⬇️:
在这里插入图片描述

编译文件⬇️:
在这里插入图片描述
⬆️编译文件的两条命令:如果第一条报错了可以用第二条,编译之后会从demo.c生成demo.o文件,然后要连接自己创建的静态库。
⬇️第一种链接生成库并执行demo程序的方式:
在这里插入图片描述
⬇️第二种链接生成库并执行demo程序的方式:

gcc -o demo -L . demo.o -lbasic
//-o输出文件的名称 
//-L . 代表当前路径
//demo.o 指定代码对象
//-lbasic l代表library,basic代表basic库的名称

在这里插入图片描述

编译过程的理解

.c文件->(compile编译之后得到).o文件(二进制的01文件)
在这里插入图片描述

静态链接库的缺点:

1.生成的可执行文件会随着静态库中调用的函数个数增长而增长。
静态库函数在被调用后,最终生成的可执行文件中是包含函数的信息的,也就是如果五个静态库中的函数被调,那么它在链接生成的可执行文件的过程中会把这五个静态库中的函数写到可执行文件里(通过链接写入),因此如果调用的静态库中的函数很多的话,生成的可执行文件会很大,这是静态函数的缺点。
2.如果要对库中的某些函数做修改,重新生成exe可执行文件的话,需要对整个文件重新编译/链接,才能实现更新。

动态链接

在程序执行时,这些库将被上传并重新加载到内存中,然后链接到特定的内存中的地址,就实现了动态链接器动态链接的作用。
因此动态库中的函数调用不会具体在可执行文件中写对应函数编译后的01文件(静态链接库的函数才会这么做),只会写该函数在内存中的地址,因此exe的大小不会很大。
在这里插入图片描述
如果有人更新了动态链接库,那么我们只需要下载更新的动态链接库,把动态链接库加载到内存中,无需重新编译程序。(好想动态库里函数的地址不变还是怎么回事?不是很懂)
在这里插入图片描述

静态函数库

在这里插入图片描述

动态函数库

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

libname.so.x.y.z
lib		固定前缀,表示它是一个库
name	动态库/共享库的名称
.so		表示它是一个动态库,静态库是.a
x		主版本号
y		次版本号
z		发行版本号

主版本号不兼容的情况,比如将原本的API中有add(int a,int b),现在的版本把add重新定义为了add(int a,int b, int c),且没有了之前的 add(int a,int b)形式的函数计算,那么就需要更改主版本号,和之前的就不兼容了。

主版本号能兼容的情况,比如将原本的API中有add(int a,int b),现在的版本把add增加定义函数复用了add(int a,int b, int c),现在关于add有两种不同传入参数形式的计算add(int a,int b)和add(int a,int b, int c),那么就不需要更改主版本号,这里保留了原来的接口,只需要改次版本号,把次版本号往高改。

静态库的改变:需要对程序进行重新编译,生成新的exe文件。

动态库的改变:升级动态库只需要替换对应改变了的动态库的文件即可,动态库的函数库没有被整合进程序,因此不需要对文件进行重新编译(关于具体改变动态库之后需不需要重新编译程序的说明如下)。

Q:改变动态库之后需不需要重新编译程序?
A:在大多数情况下,如果你只是替换了动态库(shared library)的版本而没有改变其 API(应用程序编程接口),则通常不需要对程序进行重新编译。这是因为动态链接库提供了一个接口,应用程序通过该接口与库进行通信,而不依赖于库的具体实现。
当你替换动态库的版本时,确保新版本的库仍然提供与旧版本相同的 API,这样应用程序就可以继续使用相同的接口调用。如果 API 发生了更改,可能会导致应用程序与新库不兼容,此时可能需要重新编译应用程序,以便与新的库版本匹配。
在某些情况下,如果新库版本引入了破坏性更改或者库的 ABI(应用程序二进制接口)发生了变化,你可能需要重新编译链接到该库的应用程序。破坏性更改可能包括函数签名的变化、数据结构的修改等。
总的来说,如果替换的动态库版本是兼容的(没有破坏性更改),通常可以直接替换而不需要重新编译应用程序。但是,当存在不兼容的更改时,重新编译可能是必要的。

linux系统中目录存放系统的函数库:
/lib 
/lib/x86_64-linux-gnu
/user/lib
/user/lib/x86_64-linux-gnu

库的制作

静态库的制作流程文字版

在这里插入图片描述
在这里插入图片描述
对静态库来说编译完程序之后,删掉库没有任何影响。动态库不行,编译完之后依旧不能删库。

动态库的制作流程文字版

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

静态库的制作过程(代码示例)

1️⃣先vim test.c(在其中定义它的接口)
在这里插入图片描述
2️⃣再vim test.h(定义它的头文件,申明了一下接口)
在这里插入图片描述
3️⃣在完成上面test.c和test.h文件之后,要把.c文件转换为.o文件。
gcc -c 编译汇编但是不链接。

gcc -c libtest.a test.o

4️⃣ar命令用来创建库。修改库,也可以从库中提出单个模块。Ar是archiver的缩写。
ar的-r 、-c 经常会一起使用。

ar -cr libtest.a test.o

输入命令行之后会生成test.o文件,对应的libtest.a的库。(.a是静态库)

ar的指令-t可以显示库中所包含的.o文件。

ar -t libtest.a test.o

在这里插入图片描述
5️⃣用自己定义的静态库编译函数

gcc main.c -ltest 
注意这里的-l后面只需要加上库的名字就行,libtest.a的库只用加上test就行。

虽然但是,上面的指令依旧会报错,因为gcc默认情况下只会去/lib和/user/lib搜索库文件
这时候可以把自己定义的libtest.a库cp到/lib里面
但是这种做法会污染系统目录。

这时候要在gcc里面用 -L 写链接库的存放路径
gcc main.c -L. -ltest
-L.   -L代表链接库存放在当前路径底下
	  .代表当前路径
注意这里的 -L 一定要放在 -l 之前,这样才能找的到库文件所在的目录

完成编译之后
rm rf libtest.a  //删除静态库

在命令行输入./a.out
发现依旧能运行这个文件,说明编译链接之后静态库可以删除,不会影响程序的运行
因此静态库编译之后的函数可以直接运行,不依靠环境,所以程序的可移植性强
      ./     代表当前目录,是一个相对路径前缀。
      a.out  是可执行文件的名称。

动态库的制作过程(代码示例)

1️⃣将.c文件转化为.o文件

gcc -fPIC *.c -c
注意这里的f小写,PIC大写
“ -fPIC 在上面有解释”

另外说明:共享库是在程序启动的时候被加载到内存中的,每次加载的位置会不同。

这里转换的语句是
gcc -fPIC test.c -c

2️⃣创建动态库

gcc -shared -o libxxx.so *.o
-shared   表示动态库(共享库)

这里输入命令行:
gcc -shared -o libtest.so test.o

3️⃣编译main.c文件,和之前的静态库编译过程一样,链接时都可以通过-L指定库文件的路径。

gcc -o main main.c -L. -lxxx

这里输入:
gcc -o main main.c -L. -ltest

4️⃣更改环境变量,再执行./main
如果不更改环境变量,直接执行./main会报错:error while loading shared libraries:No such file or directory ,即加载共享库时出错
这是因为动态库没有编译到可执行程序中,如果要编译的话环境中必须要提供相应的库支持。直接运行程序的时候,加载器需要把程序加载到内存中,加载器只会到系统目录即/lib和/user/lib中寻找。
同样我们可以把生成的.so文件直接粘贴到/lib中,这样执行就不会报错了,但是会污染系统目录。正确做法如下:
Linux提供了一个LD_LIBRARY_PATH的环境变量

export LD_LIBRARY_PATH="."   //设置LD_LIBRARY_PATH在当前目录下
./main.out		//然后程序就能正常运行

另外要注意库的查找优先级,加载器会先在LD_LIBRARY_PATH的路径下查找库,然后才会去系统的库目录/lib和/user/lib。
库的搜索先后路径很重要,因为这决定了gcc最终链接了哪个库。

写接口的一些习惯

这里以之前的静态库的制作过程为例

⬇️test.c.

#ifndef _TEST_C
#define _TEST_C

#include <stdio.h>
#include "test.h"

int fun_add_api(int a int b){
	printf("enter int_add_api\n");
	return a + b;
}

#endif

⬇️test.h

#ifndef _TEST_H_

#ifndef _TEST_C_
#define FUN EXTERN extern   //如果没有定义_TEST_C_的宏,就把它声明为extern
#else
#define FUN EXTERN			//如果定义了_TEST_C_的宏,就把它声明为空
#endif			//外部调用函数的时候会调用.h文件,此时_TEST_H_是为定义状态,宏_TEST_H_就会声明为extern,就实现了外部函数的分离
//如果在test.c文件中调用,test.c也会包含头文件test.h,但是test.c已经预先定义好了_TEST_C_的宏,因此在头文件里会把_TEST_C_定义为空,相当于在头文件里对函数名做了一个声明

FUN_EXTERN int fun_add_api(int,int);

#endif

(对于上面的宏_TEST_H_、 _TEST_C _ )这种在头文件中使用宏定义的方式通常是为了在同一个头文件能够适应不同的使用场景,尤其是在实现和调用的文件中。
ifndef _TEST_H _ :这是一个头文件保护宏(Header Guard Macro),用于防止头文件被多次包含。如果 TEST_H 这个宏未定义,那么继续执行下面的内容;如果已经定义了,就跳过整个部分,避免重复定义。
#ifndef _TEST_C _:这是另一个头文件保护宏,用于在头文件被 C 文件包含时定义一些宏。
如果 TEST_C 未定义,表示当前处于头文件被 C 文件包含的情境,就定义 FUN 为 EXTERN extern。这个 EXTERN 宏可能是定义为一些存储类修饰符,用于指示编译器变量的链接属性(例如 extern 表示声明但不定义变量,具体定义可能在其他地方)。
如果 TEST_C 已经定义了,表示当前处于头文件被 C 文件以外的文件包含的情境,就定义 FUN 为 EXTERN。这可能是为了在实现文件中定义具体的函数或变量。
这种做法的目的是为了在同一个头文件中,根据不同的包含情境提供不同的定义,使得头文件能够适用于不同的源文件。通常,这样的宏定义是为了在头文件中既能够被用于声明(声明变量或函数),又能够在实现文件中定义。

如何判断一个程序有没有链接动态库

在这里插入图片描述

在这里插入图片描述
在x86-64平台下编译的
在这里插入图片描述
⬆️动态链接库的.o文件

在这里插入图片描述
⬆️lib.so.6是C函数库
libtest.so是刚刚制作的动态库
ld-linux-x86-64中的ld指的就是加载器,它会在程序启动的时候加载lib.so文件到内存中。
在这里插入图片描述

-static选项

在这里插入图片描述
gcc默认优先链接动态库;加上-static之后,gcc会优先链接静态库。

静态动态混合链接

有的库需要动态链接优先,有的库需要动态链接优先。
在这里插入图片描述
在这里插入图片描述

静态库和动态库的对比

在这里插入图片描述

总结

静态库是以空间换时间;
动态库是以时间换空间。

  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃小酥肉的小波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值