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

库的制作和原理

1.库的概念

库是写好的现有的,成熟的,可以复⽤的代码。现实中每个程序都要依赖很多基础的底层库,不可
能 每个⼈的代码都从零开始,因此库的存在意义⾮同寻常。
库有两种,一种是静态库,一种是动态库,在不同的操作系统下库的后缀也是不同的:

2动静态库

在使用动静态库的时候的系统默认规则:

结论1:gcc/g++默认使用动态库!非得静态链接 只能-static!一旦-static,就必须存在对应的静态库


结论2:在linux系统下,默认情况安装的大部分库,默认都优先安装的是动态库!


结论3:库:应用程序=1:n


结论4:vs不仅仅形成可执行程序,也能形成动静态库只存在静态库,可执行程序,对于该库,只能静态链接了!

2.1.静态库的制作和使用

场景再现:

此时有两名同学,张三和李四。

李四有一个usercode.c的文件,但是李四的代码依赖张三的代码,那么如何将李四的代码给运行起来呢?

方法一:只需将张三所有的.o文件和.h文件给李四,李四再将自己的.c文件编译成.o文件,再将所有的.o文件生成可执行文件即可

方法二:当.o文件太多的话,少一个.o文件,程序都运行不了,所以此时我们就需要张三将所有.o文件进行打包处理一起交给李四(这个过程就是制作一个静态库的过程)

libmyc.a :lib是前缀 ,  .a:后缀   myc:才是真实的名字

ar -rc libmyc.a *.o

此时李四有了所有的头文件和所有.o文件打包后的.a文件,李四此时将自己的usercode.c文件编成.o文件,指定路径 指定文件进行连接(这个过程就是静态库使用的过程)

gcc -o app usercode.o -L. -l mycL .:指定路径是当前路径
l: 指定文件是 myc文件

但是还是太low了,所以我们将所有的.h文件放在include目录下 所有的.a文件放再 mylib路径下,

再将其进行打包,一起发送个李四,(这就是下载安装包的过程)

此时李四的只需依靠一个 lib就可以形成可执行文件 

 gcc -o app usercode.c -I ./lib/include/ -L ./lib/mylib/ -lmycI:.c文件编译成 .o文件 需要头文件的地址,I就是指定头文件的地址,时成功编译成 .o文件

2.2.动态库的制作和使用

动态库的制作和静态库的制作有着异曲同工之处,它们的只要的目的就是将所有的.文件进行打包和所有的.o文件进行打包,只是打包的命令不一样而已。

gcc -shared -o libmyc.so *.olib:前缀.so:后缀myc:文件名动态库的制作

此时我们像使用静态库的时候,使用动态库形成可执行文件,可以通过但是运行的时候,却不行,

ldd 查看原因系统并不知道这个动态库

解决方法:就是让系统知道有这个动态库

有四种:

1.拷⻉ .so ⽂件到系统共享库路径下, ⼀般指 /usr/lib、/usr/local/lib、/lib64 

2.向系统共享库路径下建⽴同名软连接

3.更改环境变量: LD_LIBRARY_PATH

4.ldconfig⽅案:配置/ etc/ld.so.conf.d/ ,ldconfig更新

3.ELF文件

要理解编译链链接的细节,我们不得不了解⼀下ELF⽂件。
其实有以下四种⽂件其实都是ELF件:

1.可重定位文件(Relocatable File):即xxx.0文件。包含适合于与其他目标文件链接来创建可执行件或者共享目标文件的代码和数据。


2.可执行文件(Executable File):即可执行程序。


3.共享目标文件(Shared 0bject File):即xxx.So文件。


4.内核转储(core dumps),存放当前进程的执行上下文,用于dump信号触发。

一个FLF文件有下面四个组成部分:

1.ELF头(ELF header):描述文件的主要特性。其位于文件的开始位置,它的主要目的是定位文件的其他部分。

2.程序头表(Program header table):2列举了所有有效的段(segments)和他们的属性。表里记着每个段的开始的位置和位移(offset)、长度,毕竟这些段,都是紧密的放在二进制文件中,需要段表的描述信息,才能把他们每个段分割开。


3.节头表(Section header table):包含对节(sections)的描述。


4.节(Section):ELF文件中的基本组成单位,包含了特定类型的数据。ELF文件的各种信息和数据都存储在不同的节中,如代码节存储了可执行代码,数据节存储了全局变量和静态数据等。

最常见的节


代码节(.text):用于保存机器指令,是程序的主要执行部分。


数据节(.data):保存已初始化的全局变量和局部静态变量。

3.1.ELF文件形成可执行

分两步:

step-1:将多份C/C++ 源代码,翻译成为目标.o 文件

step-2:将多份.o文件section进行合并

3.2.ELF文件可执行加载

一个ELF会有多种不同的Section,在加载到内存的时候,也会进行Section合并,形成segment合并原则:相同属性,比如:可读,可写,可执行,需要加载时申请空间等.

这样,即便是不同的Section,在加载到内存中,可能会以segment的形式,加载到一起很显然,这个合并工作也已经在形成ELF的时候,合并方式已经确定了,具体合并原则被记录在了ELF的 程序头表(Program header table)

为什么要将section合并成为segment?


1.Section合并的主要原因是为了减少页面碎片,提高内存使用效率。如果不进行合并假设页面大小为4096字节内存块基本大小,加载,管理的基本单位),如果.text部分为4097字节,.init部分为512字节,那么它们将占用3个页面,而合并后,它们只需2个页面。


2.此外,操作系统在加载程序时,会将具有相同属性的section合并成一个大的segment,这样就可以实现不同的访问权限,从而优化内存管理和权限访问控制

对于 程序头表 和 节头表 又有什么用呢,其实 ELF 文件提供2个不同的视图/视角来让我们理解这
两个部分

说⽩了就是:⼀个在链接时作⽤,⼀个在运⾏加载时作⽤

1.链接视图(Linking view) - 对应节头表 Section header table

命令 readelf -s hello.o 可以帮助査看ELF文件的 节头表。


1.text节 :是保存了程序代码指令的代码节。
2..data节:保存了初始化的全局变量和局部静态变量等数据。
3..rodata节:保存了只读的数据,如一行C语言代码中的字符串。由于.rodata节是只读的,所以只能存在于一个可执行文件的只读段中。因此,只能是在text段(不是data段)中找到.rodata节。
3.BSS节:为未初始化的全局变量和局部静态变量预留位置
4..symtab节:SymbolTable 符号表,就是源码里面那些函数名、变量名和代码的对应关系。

5.got.plt节 (全局偏移表-过程链接表):.got节保存了全局偏移表。

..6got节和.plt节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。对于GOT的理解

2.执⾏视图(execution view) - 对应程序头表 Program header table

告诉操作系统,如何加载可执⾏⽂件,完成进程内存的初始化。⼀个可执⾏程序的格式中,
⼀定有 program header table

4.理解连接与加载

4.1.静态链接

⽆论是⾃⼰的.o, 还是静态库中的.o,本质都是把.o⽂件进⾏连接的过程
所以:研究静态链接,本质就是研究.o是如何链接的

假设此时有这样的两段代码:

run调用printf

main调用 run()和printf()

此时查看它们的汇编代码

objdump -d code.o

现象是:它们的位置都被置为了0,hello.o 中的 main 函数不认识 printfrun 函数,code.o 不认识 printf 函数

原因:

其实就是在编译 hello.c 的时候,编译器是完全不知道 printf 和 run 函数的存在的,⽐如他们
位于内存的哪个区块,代码⻓什么样都是不知道的。因此,编辑器只能将这两个函数的跳转地址先暂 时设为0。

4.2.静态加载

⼀个ELF程序,在没有被加载到内存的时候,有没有地址呢?
⼀个ELF程序,在没有被加载到内存的时候,本来就有地址,当代计算机⼯作的时候,都采⽤"平坦
模式"进⾏⼯作。所以也要求ELF对⾃⼰的代码和数据进⾏统⼀编址,下⾯是 objdump -S 反汇编
之后的代码

进程mm_struct、vm_area_struct在进程刚刚创建的时候,初始化数据从哪⾥来的?

从ELF各个 segment来,每个segment有⾃⼰的起始地址和⾃⼰的⻓度,⽤来初始化内核结构中的[start, end] 等范围数据,另外在⽤详细地址,填充⻚表

4.3.动态链接和加载

动态库就不是像静态库一样需要把自己和其他,o文件共同进行链接,动态库其实是放在一个叫做共享区的地方,当需要调用的使用再去共享区里进行寻找

那为什么编译器默认不使⽤静态链接呢?
静态链接最⼤的问题在于⽣成的⽂件体积⼤,并且相当耗费内存资源。
动态链接到底是如何⼯作的?

动态链接实际上将链接的整个过程推迟到了程序加载的时候。

在C/C++程序中,当程序开始执⾏时,它⾸先并不会直接跳转到 main 函数。实际上,程序的⼊
口点  _start ,这是⼀个由C运⾏时库(通常是glibc)或链接器(如ld)提供的特殊函数。
_start 函数中,会执⾏⼀系列初始化操作,这些操作包括:
1. 设置堆栈:为程序创建⼀个初始的堆栈环境
2. 初始化数据段:将程序的数据段(如全局变量和静态变量)从初始化数据段复制到相应的内存位
置,并清零未初始化的数据段。
3. 动态链接:这是关键的⼀步, _start 函数会调⽤动态链接器的代码来解析和加载程序所依赖的
动态库(shared libraries)。动态链接器会处理所有的符号解析和重定位,确保程序中的函数调
⽤和变量访问能够正确地映射到动态库中的实际地址。

http://www.dtcms.com/a/324235.html

相关文章:

  • 双亲委派机制是什么?
  • 大模型工具集成四层架构:识别、协议、执行与实现
  • reinterpret_cast and static cast
  • Lua的数组、迭代器、table、模块
  • Elasticsearch 搜索模板(Search Templates)把“可配置查询”装进 Mustache
  • 从MySQL到大数据平台:基于Spark的离线分析实战指南
  • 重学React(四):状态管理二
  • Spark执行计划与UI分析
  • 【软考中级网络工程师】知识点之 DCC 深度剖析
  • 系统架构设计师备考之架构设计高级知识
  • 企业高性能web服务器——Nginx
  • App Trace 功能详解 (开发者视角)
  • IDEA 如何导入系统设置
  • 从0到1学LangChain之Agent代理:解锁大模型应用新姿势
  • 【机器学习深度学习】Embedding 模型详解:从基础原理到实际应用场景
  • Xstream反序列化,fastjson,jcakson靶场复现
  • 刑法视野下的虚拟财产属性争议:法律风险与市场潜力解析
  • ThinkPHP8学习篇(二):路由
  • Day39--动态规划--198. 打家劫舍,213. 打家劫舍 II,337. 打家劫舍 III
  • Code Exercising Day 10 of “Code Ideas Record“:StackQueue part02
  • MVCC和日志
  • 国内外主流大模型深度体验与横向评测:技术、场景与未来展望
  • 后置定语:for + 宾语 + 被动不定式
  • CentOS 10在文本控制台模式下修改字体大小
  • 2020/12 JLPT听力原文 问题一
  • LLM多模态模型应用探索调研
  • 【0基础3ds Max】主工具栏介绍(下)
  • 故障诊断 | VMD-CNN-LSTM西储大学轴承故障诊断附MATLAB代码
  • 智慧社区--4
  • 【C++详解】红黑树规则讲解与模拟实现(内附红黑树插入操作思维导图)