静态库和动态库

1、编译过程

1.预处理:解释并展开源程序当中的所有的预处理指令,此时生成 *.i 文件。
2.编译:词法和语法的分析,生成对应硬件平台的汇编语言文件,此时生成 *.s 文件。
3.汇编:将汇编语言文件翻译为对应处理器的二进制机器码,此时生成 *.o 文件。
4.链接:将多个 *.o 文件合并成一个不带后缀的可执行文件。

gec@ubuntu:~$ gcc hello.c -o hello.i -E
gec@ubuntu:~$ gcc hello.i -o hello.s -S
gec@ubuntu:~$ gcc hello.s -o hello.o -c
gec@ubuntu:~$ gcc hello.o -o hello -lcgcc hello.c  -o  hello

2.ELF格式

2.1概述

对于上述编译过程,重点关注最后一步库文件的链接(gcc hello.o -o hello -lc):链接实际上是将多个.o文件合并在一起的过程。这些 *.o 文件合并前是 ELF 格式,合并后也是 ELF 格式。
ELF全称是 Executable and Linkable Format,即可执行可链接格式。ELF文件由多个不同的段(section)组成,如下图所示:
在这里插入图片描述
ELF格式的合并,实际上就是将多个文件中各自对应的段合并在一起,形成一个统一的ELF文件。在此过程中,必然需要对各个 *.o 文件中的静态数据(包括常量)、函数入口的地址做统一分配和管理,这个过程就叫做 重定位,因此未经链接的单独的 *.o 文件又被称为可重定位文件,经过链接处理合并了相同的段的文件称为可执行文件。
库的本意是library图书馆,库文件就是一个由很多 *.o 文件堆积起来的集合。

2.2 相关命令

(1) readelf 可以用来查看 ELF 格式文件的具体细节:

# 查看文件格式头部信息
gec@ubuntu:~$ readelf -h a.out# 查看各个section信息
gec@ubuntu:~$ readelf -S a.out# 查看符号表
gec@ubuntu:~$ readelf -s a.out

3.库文件

3.1概述

库的本意是library图书馆,库文件就是一个由很多 *.o 文件堆积起来的集合。本质上来说库是一种可执行代码的二进制形式,这个文件可以在编译时由编译器直接链接到可执行程序中,也可以在运行时由操作系统的runtime enviroment根据需要动态加载到内存中。

3.2分类

库文件分为两类:静态库和动态库。如:

win32平台下,静态库通常后缀为.lib,动态库为.dll ;
linux平台下,静态库通常后缀为.a,动态库为.so 。静态库:libx.a
动态库:liby.so

库文件的名称遵循这样的规范:
lib库名.后缀
其中,lib是任何库文件都必须有的前缀,库名就是库文件真正的名称,比如上述例子中两个库文件分别叫x和y,在链接它们的时候写成 -lx 和 -ly ,后缀根据静态库和动态库,可以是 .a 或者 .so:

  • 静态库的后缀:.a (archive,意即档案)
  • 动态库的后缀:.so (share object,意即共享对象)
    注意:不管是静态库,还是动态库,都是可重定位文件 *.o 的集合。

3.3目的

  • 模块化:库文件将功能模块化,使得程序结构更加清晰,易于管理和维护。
  • 简化部署:使用库文件可以简化软件的部署过程,因为它们可以在不同的程序之间共享,而不需要重复包含相同的代码。
  • 动态链接:动态库文件允许在程序运行时才链接,这样可以在不重新编译程序的情况下更新库,提供了更大的灵活性。
  • 减少内存占用:使用动态库时,由于多个程序可以共享同一份库文件,因此可以减少每个程序的内存占用。
  • 易于更新和维护:库文件的更新只需要替换原有文件,而不需要重新编译使用该库的所有程序,简化了维护工作。
  • 跨平台兼容性:库文件可以被设计为跨平台使用,增加了软件的可移植性。
    总的来说,库文件的使用是为了提高软件开发的效率、灵活性和可维护性,同时减少资源的重复占用。

4、静态库

1所谓静态库,就是在静态编译时由编译器到指定目录寻找并且进行链接,一旦链接完成,最终的可执行程序中就包含了该库文件中的所有有用信息,包括代码段、数据段等。
静态链接库在程序编译时会被链接到目标代码中,目标程序运行时将不再需要改动态库,移植方便,体积较大,浪费空间和资源,因为所有相关的对象文件与牵涉到库都被链接合成一个可执行文件,这样导致可执行文件的体积较大。

2.静态库的制作
假设功能文件 a.c、b.c 包含了一些通用的程序模块,可以被其他程序复用,那么可以将它们制作成静态库,具体的步骤是:
第一步,制作 *.o 原材料
gec@ubuntu:~$ gcc a.c -o a.o -c
gec@ubuntu:~$ gcc b.c -o b.o -c

第二步,将 *.o 合并成一个静态库
gec@ubuntu:~$ ar crs libx.a a.o b.o

可见制作静态库非常简单,制作完成之后,可以用命令 ar 查看库中所包含的 *.o 文件:
gec@ubuntu:~$ ar -t libx.a

  1. 静态库的常见操作
    3.1 查看静态库中的 .o 列表
    gec@ubuntu:~$ ar t libx.a #(t意即table,以列表方式列出
    .o文件)
    a.o
    b.o

3.2 删除静态库中的 .o 文件
gec@ubuntu:~$ ar d libx.a b.o #(d意即delte,删除掉指定的
.o文件)
gec@ubuntu:~$ ar t libx.a
a.o

3.3 向静态库增加 .o 文件
gec@ubuntu:~$ ar r libx.a b.o #(r意即replace,添加或替换(重名时)指定的
.o文件)
gec@ubuntu:~$ ar t libx.a
a.o
b.o

3.4 提取静态库中的 .o 文件
gec@ubuntu:~$ ar x libx.a #(x意即extract,将库中所有的
.o文件释放出来)
gec@ubuntu:~$ ar x libx.a a.o #(指定释放库中的a.o文件)

4.静态库的使用
库文件最大的价值,在于代码复用。假设在上述库文件所包含的 *.o 文件中,已经包含了若干函数接口,那么只要能链接这个库,就无需再重复编写这些接口,直接链接即可。

使用静态库 要是用静态库libadd.a,只需要包含add.h,就可以使用函数add()、sub()。

#include <stdio.h>
#include “add.h”
void main(){
printf(“add(5,4) is %d\n”,add(5,4));
printf(“sub(5,4) is %d\n”,sub(5,4));
}
静态库的文件可以放在任意的位置,编译时只需要找到该库文件即可。
gcc -c -I /home/xxxx/include -L /home/xxxxx/lib libadd.a test.c
1). 通过-I(是大i)指定对应的头文件
2). 通过-L制定库文件的路径,libadd.a就是要用的静态库。
3). 在test.c中要包含静态库的头文件。

总结:

编译时:gcc a.c liba.a -o project
相当于liba.a 代替了b.c c.c参与编译

5、动态库

1.概述
不管是动态库还是静态库,它们都是 *.o 文件的集合。动态库指的是以.so后缀的库文件。动态库在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入,因为可执行文件体积较小。有了动态库,程序的升级会相对比较简单,比如某个动态库升级了,只需要更换这个动态库的文件,而不需要去更换可执行文件。但要注意的是,可执行程序在运行时需要能找到动态库文件。可执行文件时动态库的调用者。
在实际应用中,动态库应用场合要远多于静态库,因为虽然动态库的运行时装载特性会使得程序性能有略微的下降,但换来的是不仅仅节省了大量的存储空间,更重要的是使得主程序和库松耦合,不互相捆绑,当库升级的时候,应用程序无需任何改动即可获得新版库文件的功能,这极大地提高了程序的灵活性。

2.库文件命名
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称;动态库的名字一般为libxxxx.so.x.y.z,含义如下图所示:
此处,符号链接的作用不是“快捷方式”,而是为了可以让动态库在升级版本的时候更加方便地向前兼容。一般而言,完整的动态库文件名称是:
lib库名.so.主版本号.次版本号.修订版本号
比如: libx.so.1.3.1
当动态库迭代升级时,其版本号会发生相应的改变。比如下面的版本更迭:
2021年3月08日发布:libx.so.1.0.0
2021年4月02日发布:libx.so.1.0.1
2021年4月23日发布:libx.so.1.0.2
2021年5月18日发布:libx.so.1.0.3
2021年8月09日发布:libx.so.1.1.0
2021年9月12日发布:libx.so.1.1.1
可以看到,修订版本号的更迭会比较频繁,次版本号次之,主版本号再次之。为了避免每次版本号的修改而重新编译,动态库一般会用一个只带主版本号的符号链接来链接程序,如:

gec@ubuntu:~$ ls -l
lrwxrwxrwx 1 root root    15 Jan 16 2020 libbsd.so.0 -> libbsd.so.0.8.7
-rw-r--r-- 1 root root 80104 Jan 16 2020 libbsd.so.0.8.7
gec@ubuntu:~$

这样一来,未来不管版本号如何变迁,只要主版本号不变,那么用户链接的库名永远都是 libbsd.so.0,而无需关心具体某个版本。而如果连主版本号都发生了改变,这一般是因为库不再向前兼容,比如删除了某些原有的接口,这种情况下,用户就需要重新编译程序。

3.制作库文件常用的参数
首先需要了解gcc编译库要用到一些参数,很重要。
在这里插入图片描述
4.制作动态库
不管是静态库还是动态库,都是用来被其他程序链接的一个个功能模块。与静态库一致,制作动态库的步骤如下:
将 *.c 编译生成 *.o
将 *.o 编译成动态库

把add.c编译成动态链接库libadd.so
#第一步:将源码编译为 *.o
gcc -fPIC -o libadd.o -c add.c
#第二步:将 *.o 编译为动态库
gcc -shared -o libadd.so libadd.o

也可以直接使用一条命令
gcc -fPIC -shared -o libadd.so add.c

5.动态库的使用
动态库的编译跟静态库并无二致,如:
gec@ubuntu:~$ pwd
/home/gec
gec@ubuntu:~$ ls lib/
libx.so
gec@ubuntu:~$ gcc main.c -o main -L./lib -lx
说明:
-L 选项后面跟着动态库所在的路径。
-l 选项后面跟着动态库的名称。
运行时链接
动态库的最大特征,就是编译链接后程序并不包含动态库的代码,这些程序会在每次运行时,动态地去寻找并定位其所依赖的库文件中的模块,这是他们为什么被称为动态库的原因。
也就是说,如果程序运行时找不到动态库,运行就会失败,例如:
gec@ubuntu:~$ ./main
报错
出现上述错误的原因,就是因为运行程序 main 时,无法找到其所依赖的动态库 libx.so,解决这个问题,有三种办法:

1.编译时预告:
gec@ubuntu:~$ gcc main.c -o main -L. -lx -Wl,-rpath=/home/gec/lib
2.设置环境变量:
gec@ubuntu:~$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/gec/lib
3.将库文件拷贝到根目录下的/lib里面

总结

可以通过动态库(也称为共享库或共享对象文件)再构建一个动态库。在链接过程中,一个动态库可以依赖于其他动态库或静态库。

当你使用编译器(如gcc或clang)来构建动态库时,你可以指定其他动态库作为链接时的依赖。这些依赖的库在运行时会被动态加载。

以下是一个简单的例子,展示了如何使用gcc来从一个动态库(libA.so)构建一个依赖于它的新动态库(libB.so):

编译和链接第一个动态库(libA.so)
假设你有一个源文件a.c,你可以这样编译和链接它:

gcc -shared -o libA.so a.c

编译和链接第二个动态库(libB.so),它依赖于libA.so
假设你有一个源文件b.c,它调用了在libA.so中定义的函数。为了构建libB.so,你需要链接到libA.so:

gcc -shared -o libB.so b.c -L. -lA

注意-L.选项告诉链接器在当前目录(.表示当前目录)中查找库,而-lA选项告诉链接器链接到名为libA.so的库(注意,在-l选项后,库名通常不包含前缀lib和后缀.so)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/36282.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

便携式烟气监测仪的应用主要有哪些?

烟气分析仪是一种用于检测和分析烟气中各种成分和污染物含量的仪器&#xff0c;通过采集和处理烟气样品&#xff0c;对其中的各种成分进行定量分析。那么&#xff0c;便携式烟气监测仪的应用主要有哪些&#xff1f;为方便大家了解&#xff0c;下面就让小编来为大家简单介绍一下…

2-2到2-4

计算出所有人的平均年龄&#xff1a; val lines sc.textFile("/root/data/scala/people/page.txt") val count lines.count() val total lines.map(line > line.split(" ")(1)).map(t>t.trim.toInt).collect().reduce((a,b)>ab) val avgAge …

如何防止SQL注入

为了防止SQL注入攻击&#xff0c;可以采取以下一系列的安全措施&#xff0c;这些措施结合了多篇参考文章中的关键信息和方法&#xff1a; 使用参数化查询或预编译语句&#xff1a; 这是防止SQL注入的最常见且最有效的方法之一。通过将用户输入的数据作为参数传递给SQL查询语句…

[Python]根据文件路径获取文件所在目录、文件名和后缀名

一、简介 本文介绍了在python中如何根据文件的路径名字&#xff08;字符串&#xff09;获取文件所在目录名、文件名&#xff08;带后缀&#xff09;、文件名&#xff08;无后缀&#xff09;和文件后缀名。 二、代码 假设文件路径为/home/user/temp.txt&#xff0c;使用以下代…

压缩pdf文件大小的方法,如何压缩pdf格式的大小

pdf太大怎么压缩&#xff1f;当你需要通过电子邮件发送一个PDF文件&#xff0c;却发现文件太大无法成功发出时&#xff0c;这些情况下&#xff0c;我们都需要找到一种方法来压缩PDF文件&#xff0c;以便更便捷地进行分享和传输。PDF文件的大小通常与其中包含的图片、图形和文本…

入门JavaWeb之 Response 下载文件

web 服务器接收到客户端的 http 请求 针对这个请求&#xff0c;分别创建一个代表请求的 HttpServletRequest 对象&#xff0c;代表响应的 HttpServletResponse 对象 获取客户端请求过来的参数&#xff1a;HttpServletRequest 给客户端响应一些信息&#xff1a;HttpServletRe…

数据库索引失效的11种情况

MySQL中 提高性能 的一个最有效的方式是对数据表 设计合理的索引。索引提供了高效访问数据的方法&#xff0c;并且加快查询的速度&#xff0c;因此索引对查询的速度有着至关重要的影响。使用索引可以 快速地定位 表中的某条记录&#xff0c;从而提高数据库査询的速度&#xff0…

js获取选中区域(window.getSelection的基本使用)

返回一个 Selection 对象&#xff0c;表示用户选择的文本范围或光标的当前位置。 const selection window.getSelection() 1.toString() //光标选中的文本 const selectedText selection.toString() 2.getRangeAt() //返回一个包含当前选区内容的区域对象。 selection…

数据与文字的表示方法

目录 1. 数据格式 1. 文本文件格式 2. 二进制文件格式 3. 数据库格式 4. 压缩格式 2. 数字机器码表示 整数表示 浮点数表示 3. 字符与数组的表示方法 1. ASCII&#xff08;美国信息交换标准代码&#xff09; 2. 扩展ASCII 3. Unicode 4. UTF-8&#xff08;8 位 Uni…

面试相关-接口测试常问的问题

1.为什么要做接口测试 (1)现在大多系统都是前后端分离的项目,前端和后端的进度可能不一样,那为了尽早的进入测试,前端界面没有开发完成的情况下,只要后端的接口开发完了,就可以提前做接口测试了; (2)基于安全考虑,只依赖前端进行限制,已经完全不满足系统的安全性…

Power Pivot——常用DAX 函数

常用DAX 函数 以下这些函数是 DAX 中最常用的一部分&#xff0c;通过熟练掌握这些函数&#xff0c;你可以有效地进行数据分析和建模。 聚合函数 (Aggregation Functions) SUM() 用途&#xff1a;对指定列中的所有数值求和。 语法&#xff1a;SUM() 示例&#xff1a;SUM(Sale…

重生之我要学后端01--后端语言选择和对应框架选择

编程语言 后端开发通常需要掌握至少一种编程语言。以下几种语言在后端开发中非常流行&#xff1a; Java&#xff1a;广泛用于企业级应用程序。Python&#xff1a;因其易学性和强大的库支持&#xff08;如Django和Flask&#xff09;而受欢迎。Node.js&#xff08;JavaScript&a…

电商卖家怎么快速采集复制1688全店宝贝到自己店铺?淘/猫/拼/抖都适用!

1688上面的货源品类丰富&#xff0c;很多卖家都是在这里找厂家&#xff0c;当我们找好厂家后&#xff0c;怎么将厂家店铺里所有宝贝都复制到自己店铺呢&#xff1f; 虽然1688平台本身支持铺货到其他平台&#xff0c;但一个个铺货太耗费时间了。 阿里巴巴中国站获得1688商品详…

【AI大模型RAG】深入探索检索增强生成(RAG)技术

目录 1. 引言2. RAG技术概述2.1 RAG技术的定义2.2 RAG技术的工作原理2.3 RAG技术的优势2.4 RAG技术的应用场景 3. RAG的工作流程3.1 输入处理3.2 索引建立3.3 信息检索3.4 文档生成3.5 融合与优化 4. RAG范式的演变4.1 初级 RAG 模型4.2 高级 RAG 模型4.3 模块化 RAG 模型优化技…

会计报表分析

目录 一. 会计报表的种类 \quad 一. 会计报表的种类 \quad 反应财务状况的是资产负债表 反应经营成果的是利润表 有时间点的就是静态表 动态表就是有一个区间的, 比如一年, 一个季度等

探索这些有趣的API,让你的应用与众不同

在这个由数据驱动的时代&#xff0c;我们每天都在与各种应用程序和服务互动&#xff0c;却很少意识到它们背后的技术奇迹。API&#xff0c;作为这些互动的幕后英雄&#xff0c;不仅简化了开发过程&#xff0c;还扩展了技术的边界。有趣的API&#xff0c;特别是那些能够激发创新…

QT 如何储存多种数据类型(QVariant )

QVariant 是 Qt 框架中用于存储各种数据类型的类。它提供了一个强大的类型系统&#xff0c;允许你在运行时存储和检索多种类型的数据&#xff0c;而不需要在编译时确定类型。QVariant 的主要优点在于它的灵活性和通用性&#xff0c;这使得它在 Qt 的很多组件和机制中都被广泛使…

时间戳是什么,如何使用时间戳

时间戳&#xff08;Timestamp&#xff09;是表示特定时间点的数值&#xff0c;通常以自1970年1月1日00:00:00 UTC&#xff08;协调世界时&#xff09;以来的秒数或毫秒数来表示。这个时间点被称为Unix纪元&#xff08;Unix epoch&#xff09;。时间戳广泛用于计算机系统中&…

数据结构教材关于C/C++的研究

变量 指针 引用 变量 普通变量表示一个内存空间&#xff0c;直接printf是内存空间里的值 结构体 定义一个结构体类型变量为什么必须用指针&#xff1f; 因此无法确定结构体需要多少空间&#xff0c;改用指针可以解决这个问题&#xff0c;因为指针的大小是固定的 指针 指…

HTTP协议和Nginx

一、HTTP协议和Nginx 1.套接字Socket 套接字Socket是进程间通信IPC的一种实现&#xff0c;允许位于不同主机&#xff08;或同一主机&#xff09;上不同进程之间进行通信和数据交换&#xff0c;SocketAPI出现于1983年BSD4.2实现在建立通信连接的每一端&#xff0c;进程间的传输…