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

Linux动静态库

铺垫

1.动态库与静态库本质上都是文件。

2.关于动静态链接

动态链接:将库中需要的函数地址,填入可执行程序,建立关联。

优点:节省资源,不会出现太多重复代码。

缺点:对库的依赖性比较强,一旦库丢失,所有使用这个库的程序将无法运行。

静态链接:将库中的方法的实现拷贝到我们可执行程序中。

优点:不依赖库,同类型平台中可以直接使用。

缺点:可执行程序的体积比较大,比较浪费资源。

3.默认编译程序,用的是动态链接的,如果要静态链接 需要加上 -static

4.库的命名一般是libXXX.so(动态库)    libXXX.a(静态库),真实的库名称是XXX的部分。

动静态库的制作和使用

为什么要有库?

1.提高开发效率

2.隐藏源代码

首先写几个简单的.c .h代码

myprint1.h
  1 #pragma once                                                                                        
  2 #include<stdio.h>
  3 
  4 void print1();
myprint1.c
  1 #include"myprint1.h"
  2 
  3 void print1()
  4 {
  5   printf("hello 111\n");                                                                            
  6 }
myprint2.h
  1 #pragma once                                                                                        
  2 #include<stdio.h>
  3 
  4 void print2();
 myprint2.c
  1 #include"myprint2.h"
  2 
  3 void print2()
  4 {
  5   printf("hello 222\n");                                                                            
  6 }
main.c
  1 #include"myprint1.h"                                                                                                                                                                                           
  2 #include"myprint2.h"
  3 
  4 int main()
  5 {
  6   print1();
  7   print2();
  8   return 0;
  9 }

以往都是直接进行编译

gcc -c myprint1.c
gcc -c myprint2.c
gcc -c main.c

 生成同名.o

最后链接形成可执行文件

gcc -o test.exe main.o myprint1.o myprint2.o

总结如下图 


静态库文件的制作

首先编译成.o的文件

gcc -c myprint1.c
gcc -c myprint2.c

打包.o的所有文件成为libmyc.a

ar -rc libmyc.a myprint1.o myprint2.o

-c(creat)如果没有libmyc.a则自动创建

-r(replace)如果已经有libmyc.a,且myprint1.o myprint2.o内容发生改动,则重新创建libmyc.a替换原文件

最后生成libmyc.a静态库文件。


静态库文件的使用

进行编译链接main.c文件

gcc main.c -lmyc -L .

gcc 要编译的文件 -l库的名字 -L 库所在的路径

这里编译器会在指定的目录下寻找库,不会在当前目录下寻找,所以加上-L选项指定路径让编译器寻找。 

生成a.out可执行文件。


动态库文件的制作

gcc -c -fPIC myprint1.c
gcc -c -fPIC myprint2.c

-fPIC:产生位置无关码

形成.o文件。

动态库打包,不需要其他工具,只用gcc。
gcc -shared -o libmyc.so myprint1.o myprint2.o

生成libmyc.so动态库文件。


动态库文件的使用

gcc main.c -L. -lmyc
-L要链接的库在哪个路径下
-l要链接的库

最后产生a.out可执行文件。

注意:

如果头文件不在当前目录,编译时需要使用 -I 选项。

总结来说:

gcc -o mytest main.c  -L链接库所在路径  -l链接库名字  -I头文件的路径

一个小指令:

ldd a.out
查看可执行文件链接的库文件

 编写库的人:未来要给别人(用库的人),交付的是:头文件+库文件,不给源代码。

总结

文件在编译成可执行文件后,

1.如果链接的是静态库,运行时就不需要静态库了。

原因是:静态链接是将库中的方法的实现拷贝到我们可执行程序中。

2.如果链接的是动态库,运行时还需要动态库。

对于动态库:

编译时的库搜索路径 ---- 编译器gcc

运行时的库搜索路径 ---- 操作系统OS

二者可以相同,也可以不同,操作系统通常是去系统默认的搜索路径(Linux中默认为/lib64 )去搜索库文件,编译器也默认去/lib64中搜索(可以通过指令更改 gcc -o mytest main.c  -L链接库所在路径  -l链接库名字 )

3.在编译的预处理阶段已经将头文件展开在目标文件里了,所以运行时不需要头文件。

解决运行时动态库找不到问题的四种方法

1.最普通的方法(库比较官方时推荐)

把库拷贝到操作系统默认的搜索路径(Linux中默认为/lib64 ),既可以支持编译,又可以支持运行。

2.环境变量法

查看LD_LIBRARY_PATH环境变量
echo $LD_LIBRARY_PATH
把运行时需要查找库的目录添加
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:运行时需要查找库的路径

缺点:重启机器后,环境变量会恢复。

解决方法:需要更改配置文件,这样机器每次启动时读取配置文件,都会把目录加上。

在初始目录下,有2个配置文件 .bash_profile  .bashrc

打开 .bashrc文件

看到最后一行,就是每次重启时配置的环境变量,在最后追加上库的路径即可(:冒号作为分隔符)。 

3.软链接方法(库不是官方,个人写的,推荐这种方法)

sudo ln -s /动态库的绝对路径  /lib64/同动态库名

这时在/lib64目录下有一个动态库的同名文件指向原路径下的动态库。 

4.配置文件法

/etc/ld.so.conf.d 路径下建立一个文件 文件名.conf   在该文件下放置动态链接库的路径即可。

/etc/ld.so.conf.d 的含义:ld:load加载  .so:动态库  .conf:配置文件  .d:目录

最后要让刚才的配置文件生效使用如下命令。

sudo ldconfig

关于同时存在动静态库时链接问题

1.如果同时提供动态库和静态库,gcc默认使用动态库

2.如果非要使用静态库,就必须使用static选项

3.如果只提供静态库,则只能对该库进行静态链接,但是程序不一定整体是静态链接的。

4.如果只提供动态库,默认是只能动态链接,若强行静态链接,会报错!

理解动态库加载

1.系统角度理解动态库加载

动态库加载后会被映射到进程的共享区中。

当进程1的代码mytest1需要用到myc动态库时,OS将该库加载到物理空间,进而通过页表映射到进程1的虚拟地址空间的共享区中,以使用。


添加一个进程,当进程2的代码mytest2再次需要用到 myc动态库时,直接将物理空间中该动态库的代码和数据,通过页表映射到进程2的虚拟地址空间的共享区中即可。

库只需要被加载一次,然后在不同的进程地址空间中进行映射,就可以使用该库,这就是为什么叫做动态库,共享库。


一些问题:

1.谁来决定,哪些库加载了,哪些库没加载?OS会自动完成。

2.系统中可不可以同时存在非常多已经加载的库?肯定的。

操作系统要对这些库进行管理,通过一个结构体struct loadlib进行描述已经加载的库,再通过链表进行增删查改管理。

struct loadlib
{
    char* libname;
    void* addr;
    ……
    struct loadlib* next; 
}

2.关于编址

可执行程序本身是有自己的格式信息的。 

1.如果我们的可执行程序,没有加载到内存中,我们的可执行程序有没有地址(每行代码生成的指令的地址) ?有,程序通过编译后就有了地址。我们来验证。

objdump -S a.out > test.s

在 Linux 中,使用 objdump -S a.out > test.s 命令可以将可执行文件 a.out 的反汇编代码(-S选项:混合源代码和汇编代码)输出到 test.s 文件中。

生成的 test.s 文件包含:

  • 机器指令的汇编表示(如 movcall)。

  • 虚拟地址(逻辑地址):每条指令在进程虚拟地址空间中的位置。

可以看到可执行程序在没有加载进内存时就已经有了地址(每行代码生成的指令的地址) 

其实我们的可执行程序,在没有加载之前,也已经基本上被按照类别(比如权限,访问属性等)已经将可执行程序划分成为各个区域了。如下:

size a.out

2.我们进程地址空间里面的很多地址数据属性数据,是从可执行程序来的。

程序执行的起始地址,存储在可执行文件头(如ELF的e_entry字段)。操作系统加载程序后,CPU从此地址开始执行。

编址的两种方式:绝对编址,相对编址

虚拟地址(线性地址:通常指分页机制中的地址(虚拟地址空间中的地址),即虚拟地址通过页表转换后的地址称为物理地址

平坦模式:Linux将代码段、数据段等所有段的基址(Base Address)设为0,段限长(Limit)设为最大值。

绝对编址:是指在编译和链接中确定所有代码和数据的固定地址,这意味着程序中的每个指令和数据项都使用固定的、绝对的内存地址进行引用。这种编址方式也称为平坦模式

绝对编址的范围:32位平台下 [0,4GB]

对于代码和数据的每一个地址都是绝对确定的,这就是未来的虚拟地址,可以对应到虚拟地址空间,填充到页表。

虚拟地址空间本身不仅是OS要遵守,编译器编译程序时也要遵守。

相对编址(逻辑地址:是指地址是相对于某个段的偏移量,通常用于表示程序中的变量、函数等的位置。逻辑地址在编译时生成,并且与具体的内存布局无关。通俗来说,如果我们将数据段与代码段分开,代码段从0开始,数据段从0开始,通过偏移量来描述地址的方式,称为相对编址。

Linux通过平坦模式(段基址为0),使逻辑地址的偏移量直接等于线性地址(虚拟地址),后续仅需分页机制转换为物理地址,从而简化内存管理。

总结:Linux逻辑地址 = 虚拟地址(线性地址)

3.理解动态库动态链接和加载的问题

a.一般程序加载

几个问题:

1.地址空间既然是一个数据结构的对象,那么谁来用什么数据初始化?

可执行程序加载时,会用可执行程序头部相关字段,来把地址空间这个结构体对象进行初始化。

所以不同程序就会有不同的正文区域,初始化区域,未初始化区域的大小。

2.代码是数据吗?是的。

所谓的地址空间,本质是由操作系统+编译器+CPU,三者共同配合完成的! 


b.动态库的加载

静态库没有这一说法,一旦静态库被使用,那么静态库文件以绝对编址的方式写入代码中,随代码加载进物理地址。

对于动态库加载:

库的数据和方法的访问,都是通过库所在地址空间的起始地址+程序内部的偏移量即可。

相关文章:

  • *PyCharm 安装教程
  • for循环可遍历但不可以修改列表原因分析
  • 集成开发环境GoLand安装配置结合内网穿透实现ssh远程访问服务器
  • 18-除自身以外数组的乘积
  • P8716 [蓝桥杯 2020 省 AB2] 回文日期
  • 力扣-贪心-45 跳跃游戏
  • 【分布式数据一致性算法】Gossip协议详解
  • 【Rust中级教程】2.7. API设计原则之灵活性(flexible) Pt.3:借用 vs. 拥有、`Cow`类型、可失败和阻塞的析构函数及解决办法
  • 使用ESP-IDF来驱动INMP441全向麦克风
  • Python游戏编程之赛车游戏2
  • 【数据结构】(12) 反射、枚举、lambda 表达式
  • 苍穹外卖中的模块总结
  • Locale+Jackson导致Controller接口StackOverflowError异常解决
  • vue:vite 代理服务器 proxy 配置
  • TSMaster【第八篇:首战成名——第一个仿真工程实录(完整3000字版)】
  • Python深度学习:遥感影像目标识别中的数据标注技巧
  • 数据库增删查改sql语句
  • at32f103a+rtt+AT组件+esp01s 模块使用
  • Neo4j使用neo4j-admin导入csv数据方法
  • [特殊字符] Elasticsearch 双剑合璧:HTTP API 与 Java API 实战整合指南
  • 中国防疫队深入缅甸安置点开展灾后卫生防疫工作
  • 总书记考察的上海“模速空间”,要打造什么样的“全球最大”?
  • 国家发改委下达今年第二批810亿超长期特别国债资金,支持消费品以旧换新
  • 武汉一季度GDP为4759.41亿元,同比增长5.4%
  • 专业竞演、剧场LIVE直播,32位越剧新星逐梦上海
  • 加总理:目前没有针对加拿大人的“活跃威胁”