【Linux静态库和动态库】

news2024/9/21 22:46:27

Linux静态库和动态库

  • 1. 编译与ELF格式
  • 2. 库的基本概念
  • 3.静态库的制作:(假设要将a.c、b.c制作成静态库)
  • 4.静态库的常见操作
  • 5.静态库的使用
  • 6. 多个库的相互依赖
    • 举例1.(库文件制作、错误处理)
  • 7.静态库和动态库的关系和区别
  • 8.动态库的制作
    • 软链接 | 硬链接
    • 软连接和硬链接区别

在这里插入图片描述


1. 编译与ELF格式

在这里插入图片描述

预处理:解释并展开源程序当中的所有的预处理指令,此时生成 *.i 文件。(宏替换)
命令:gec@ubuntu:~$ gcc hello.c -o hello.i -E
编译:词法和语法的分析,生成对应硬件平台的汇编语言文件,此时生成 *.s 文件。
命令:gec@ubuntu:~$ gcc hello.i -o hello.s -S
汇编:将汇编语言文件翻译为对应处理器的二进制机器码,此时生成 *.o 文件。
命令:gec@ubuntu:~$ gcc hello.s -o hello.o -c
链接:将多个 *.o 文件合并成一个不带后缀的可执行文件。
命令:gec@ubuntu:~$ gcc hello.o -o hello -lc

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

2. 库的基本概念

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

静态库:libx.a
动态库:liby.so
lib库名.后缀

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

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

3.静态库的制作:(假设要将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

4.静态库的常见操作

#查看 *.o 文件
gec@ubuntu:~$ ar t libx.a  #(t意即table,以列表方式列出*.o文件)
a.o
b.o
# 删除 *.o 文件
gec@ubuntu:~$ ar d libx.a b.o #(d意即delte,删除掉指定的*.o文件)
gec@ubuntu:~$ ar t libx.a
a.o
# 增加 *.o 文件
gec@ubuntu:~$ ar r libx.a b.o #(r意即replace,添加或替换(重名时)指定的*.o文件)
gec@ubuntu:~$ ar t libx.a
a.o
b.o
# 提取 *.o 文件
gec@ubuntu:~$ ar x libx.a #(x意即extract,将库中所有的*.o文件释放出来)
gec@ubuntu:~$ ar x libx.a a.o #(指定释放库中的a.o文件)

5.静态库的使用

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

假设a.c中包含了如下函数:

// a.c
void func()
{
    printf("我是a.c中的函数func\n");
}

那么,就可以使用链接库的形式,使用这个接口:

// main.c
void func(); //函数要声明
int main()
{
    func();
    return 0;
}

编译并运行的结果是: -L/库路径 -l库名 -i 头文件路径

gec@ubuntu:~$ gcc main.c -L/home/gec -lx -o main
gec@ubuntu:~$ ./main
我是a.c中的函数func
gec@ubuntu:~$

注意:

编译语句中的 -L/home/gec 指明库文件 libx.a 的具体位置,否则系统找不到该库文件。 编译语句中的 -lx
指明要链接的库文件的具体名称,注意不包含前缀后缀。 对于静态库而言,由于编译链接时会将 main.c
所需要的库代码复制一份到最终的执行文件中,这直接导出静态库的如下特性: 执行程序在编译之后与静态库脱离关系,其执行也不依赖于静态库。
执行程序执行时由于不依赖静态库,因此也省去了运行时动态。

6. 多个库的相互依赖

假设有两个库文件:liba.a 和 libb.a,它们分别只包含了 a.o 和 b.o,假设这两个源程序有如下依赖关系:

// a.c
#incldue <stdio.h>
void fa()
{
    printf("Hey!\n");
}

// b.c
#incldue <stdio.h>
void fa();  //fa()在这里被声明了
void fb()
{
    fa(); // fb() 调用了 fa(),即libb.a依赖于liba.a
}

很明显,b.c中的功能接口是依赖于 a.c 的,换句话说,库文件 libb.a 是依赖于 liba.a 的。
现在再来写一个调用 fb() 的主函数:

void fb();
int main(void)
{
    fb();
}

编译情况如下:

gec@ubuntu:~$ gcc main.c -o main -L. -lb -la                     //谁要用我的方法,谁就在我的前面
gec@ubuntu:~$ gcc main.c -o main -L. -la -lb
./libb.a(b.o): In function `fb':
b.c:(.text+0xa): undefined reference to `fa'
collect2: error: ld returned 1 exit status

从以上编译信息来看,得出结论:

当编译链接多个库,且这些库之间有依赖关系时,被依赖的基础库要放在编译语句的后面。 在以上示例中,库 libb.a 依赖于 liba.a,即
liba.a 是被依赖的基础库,因此 -la 要放在 -lb 的后面才能通过编译。 注意:以上结论对于静态库、动态库都适用。

举例1.(库文件制作、错误处理)

【1】将常用的文件IO函数封装成自报错的静态库和动态库,并在要以后的程序中根据需要调这些库文件。read.c

例如:
gcc read.c -o read.o -c   生成连接文件
ar  crs  libread.a read.o    生成静态库 
ssize_t Read(int fd, void *buf, size_t count)
{
    int total = 0;
    int n;
    while(count > 0)
    {
        while((n=read(fd, (char *)buf+total, count))==-1 &&
               errno == EINTR);
        //(char *)buf+total :  buf指针,指向每次读取到的数据  加total确保数据不被覆盖  准确定位数据增加后每次的位置
        if(n == -1) // 遇到了错误
        {
            perror("read失败");
            return -1;
        }
        if(n == 0) // 遇到了文件尾时=0
            break;
        count -= n;  //n每次读到的大小   count 总的大小   count = count-n  :减去每次读的数据
        total += n;  //total统计每次读到的数据   total =total+n  
    }

    // 返回总共读到的字节数
    return total;
}

7.静态库和动态库的关系和区别

静态库(相当于书店,只卖不借)
原理:编译时,库中的代码将会被复制到每一个程序中
优点:程序不依赖于库、执行效率稍高
缺点:浪费存储空间、无法对用户升级迭代
动态库(相当于图书馆,只借不卖)
原理:编译时,程序仅确认库中功能模块的匹配关系,并未复制
缺点:程序依赖于库、执行效率稍低
优点:节省存储空间、方便对用户升级迭代

8.动态库的制作

lib库名.后缀 对于动态库而言,在后缀后面还经常会带着版本号: lib库名.后缀.版本号 完整的动态库文件名称是:
lib库名.so.主版本号.次版本号.修订版本号,比如: libx.so.1.3.1 动态库一般会用一个只带主版本号的符号链接(软连接ln
-s 生成)来链接程序(方便版本更新换代),

软链接 | 硬链接

1、软链接就是:“ln –s 源文件 目标文件”,只会在选定的位置上生成一个文件的镜像,不会占用磁盘空间,类似与windows的快捷方式。
2、硬链接ln源文件 目标文件,没有参数-s, 会在选定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化。

2.通过实验加深理解
[oracle@Linux]$ vi      test.log                         
#创建一个测试文件f1
[oracle@Linux]$ ln   test.log    test1.log           
#创建f1的一个硬连接文件test1.log
[oracle@Linux]$ ln  -s   test.log   test2.log      
#创建f1的一个符号连接文件test2.log
[oracle@Linux]$ ls   -li                                 
# -i参数显示文件的inode节点信息

软连接和硬链接区别

1.软连接有个主体,删除其他一个不会影响,其余的连接,但是删除主体(源文件),所有文件都失效变红
2.硬链接主次不分,只要生成以后,删除谁都不影响。
如:

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

动态库的制作
不管是静态库还是动态库,都是用来被其他程序链接的一个个功能模块。与静态库一致,制作动态库的步骤如下:

将 *.c 编译生成 *.o
将 *.o 编译成动态库
例如:
gec@ubuntu:~$ ls
a.c b.c
# 第一步:将源码编译为 *.o 
gec@ubuntu:~$ gcc a.c -o a.o -c -fPIC
gec@ubuntu:~$ gcc b.c -o b.o -c -fPIC
gec@ubuntu:~$ ls
a.c b.c a.o b.o
# 第二步:将 *.o 编译为动态库
gec@ubuntu:~$ gcc -shared -fPIC -o libx.so a.o b.o
gec@ubuntu:~$ ls
a.c b.c a.o b.o libx.so
# 第三步:将连接库生成可执行文件
gec@ubuntu:~$ gcc main.c -o main -L./lib -lx

解释:当前目录lib文件夹下 -l库名 库被放在了/lib下
说明:
-L 选项后面跟着动态库所在的路径。
-l 选项后面跟着动态库的名称

如果程序运行时找不到动态库,运行就会失败,例如:

gec@ubuntu:~$ ./main

报错

出现上述错误的原因,就是因为运行程序 main 时,无法找到其所依赖的动态库 libx.so,解决这个问题,有三种办法:
编译时预告:

gec@ubuntu:~$ gcc main.c -o main -L. -lx -Wl,-rpath=/home/gec/lib

设置环境变量:

gec@ubuntu:~$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/gec/lib

修改系统默认库路径:(不推荐)

gec@ubuntu:~$ sudo vi /etc/ld.so.conf.d/libc.conf
gec@ubuntu:~$ sudo ldconfig

在以上文件中,添加动态库所在路径即可。 注意: 此处要小心编辑,一旦写错可能会导致系统无法启动,这是一种 污染 系统的做法,不推荐。
在gcc中,如果遇到动态库和静态库同名,则会默认优先进行动态链接,如果此时需要用静态链接,可以使用编译选项 -static 达到此目的

gec@ubuntu:~$ ls ./lib  # 假设有两个重名的静态、动态库
libx.a  libx.so
gec@ubuntu:~$ 
gec@ubuntu:~$ gcc main.c -o main1 -L./lib -lx
gec@ubuntu:~$ gcc main.c -o main2 -L./lib -lx -static
gec@ubuntu:~$ ls -l
total 868
drwxr-xr-x 2 gec gec   4096 14 17:45 lib/
-rwxr-xr-x 1 gec gec   8288 14 17:46 main1 # 默认动态链接
-rwxr-xr-x 1 gec gec 845120 14 17:46 main2 # 指定静态链接
-rw-r--r-- 1 gec gec     56 14 17:42 main.c
gec@ubuntu:~$ 

很明显可以看到,静态链接的文件尺寸要大得多。


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

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

相关文章

ADI Blackfin DSP处理器-BF533的开发详解25:USB接口设计(含源代码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 硬件设计原理图 功能介绍 跟网口一样&#xff0c;USB这个设计很勉强&#xff0c;因为BF533并不带USB口&#xff0c;当时是给一个大厂做MP4的方案…

猿如意中的【UltraEdit】开发工具详情介绍

目录 一、工具名称 二、下载安装渠道 2.1 什么是猿如意&#xff1f; 2.2 如何下载猿如意&#xff1f; 2.3 如何在猿如意中下载UltraEdit&#xff1f; 三、UltraEdit介绍 四、软件安装过程 五、软件界面 六、UltraEdit功能特点介绍 七、UltraEdit使用/体验感受…

Python测试进阶(二)

文章目录简介selenium等待Web控件交互表单操作多窗口frame多浏览器处理用 js 操作文件上传弹窗pageObject复用浏览器简介 这部分主要介绍 web 自动化测试 selenium 专门测试 web 的工具&#xff0c;根据测试用例&#xff0c;直接在浏览器上执行对应动作还是在 pytest 框架中…

牛客网SQL入门复健小练

SQL3&#xff1a;distinct 查询结果去重&#xff0c;返回所有不同的university 方法一&#xff1a;distinct关键字。&#xff08;注意&#xff1a;这个关键字实际上是 select distinct&#xff0c;如果是多列&#xff0c;多列作为一个组合然后 distinct 去重&#xff09; 方…

[oeasy]python0028_直接运行_修改py文件执行权限_设置py文件打开方式

直接运行 回忆上次内容 我们把两个程序整合起来了 可以持续输出当前时间每秒都更新但是我想在 shell 里面 只输入文件名(./sleep.py)并回车就能不断输出时间可能吗&#xff1f;&#x1f914; import time while True:print(time.asctime())time.sleep(1) 尝试执行 第 1 句 根…

EXCEL基础:数据透视表(按月或月累计统计操作)

【按月统计数据】&#xff1a; 本操作实现的是原始数据是以日为单位&#xff0c;统计使用的时候&#xff0c;需要以月份或者季度、年份的形式进行&#xff0c;可以使用数据透视表的【组合】功能来 实现&#xff0c;如下所示&#xff1a; 如下所示&#xff0c;将日期字段放在行…

免费分享一套基于VuePress开发的markdown产品文档

vuepress-theme-jingli 文档 | 效果 | 本仓库的gitee镜像 &#xff08;进入赞助商扫码注册可为本项目作者充电~&#xff09; 介绍 这个主题的初衷是打造一个好用的、面向程序员的知识管理工具轻松构建一个结构化的知识库&#xff0c;让你的知识海洋像一本本书一样清晰易读。…

Mac 下设置VScode 背景图片失败解法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言解法一&#xff1a;直接修改VScode 的CSS解法二&#xff1a;还是想用插件总结前言 仓库推荐 C/C 每日一练小仓库&#xff0c;慢慢学习C 知识必备仓库 https://…

Java8:SPI机制

参考资料&#xff1a; 《双亲委派机制及其弊端》 《Java中SPI机制深入及源码解析》 《Java SPI思想梳理》 《深入理解 Java 中 SPI 机制》 写在开头&#xff1a;本文为学习后的总结&#xff0c;可能有不到位的地方&#xff0c;错误的地方&#xff0c;欢迎各位指正。 目录 …

MyBatis-Plus联表查询的短板,终于有一款工具补齐了

mybatis-plus作为mybatis的增强工具&#xff0c;它的出现极大的简化了开发中的数据库操作&#xff0c;但是长久以来&#xff0c;它的联表查询能力一直被大家所诟病。一旦遇到left join或right join的左右连接&#xff0c;你还是得老老实实的打开xml文件&#xff0c;手写上一大段…

10.泛型算法

文章目录*泛型算法**10.1概述**10.2初识泛型算法**10.2.1只读算法**算法和元素类型**操作两个序列的算法**10.2.2写容器元素的算法**算法不检查写操作**介绍back_inserter**拷贝算法**10.2.3重排容器元素的算法**消除重复单词**使用unique**10.3定制操作**10.3.1向算法传递函数…

【JavaSE】那些异常

目录 1. 何为异常 2. 异常的分类 2.1 运行异常 / 非受查异常 &#xff1a; 3. 异常的处理思想 4. 异常的抛出 5. 异常的捕获 5.1 异常声明 throws 5.2 try-catch 捕获异常并处理 6. finally 7. 异常的处理流程 8. 自定义异常 1. 何为异常 在Java中&#xff0c;将程序执…

LeetCode 1884. 鸡蛋掉落-两枚鸡蛋 -- 动态规划

鸡蛋掉落-两枚鸡蛋 中等 60 相关企业 给你 2 枚相同 的鸡蛋&#xff0c;和一栋从第 1 层到第 n 层共有 n 层楼的建筑。 已知存在楼层 f &#xff0c;满足 0 < f < n &#xff0c;任何从 高于 f 的楼层落下的鸡蛋都 会碎 &#xff0c;从 f 楼层或比它低 的楼层落下的鸡蛋…

Jedis解读与建议

1. Jedis是什么&#xff1f; 官网 Jedis 是官方推荐的java客户端&#xff01;SpringBoot的RedisTemplate的底层也是Jedis&#xff1b; 2. 为什么使用池化&#xff1f; 背景&#xff1a; Redis为单进程单线程模式&#xff0c;采用队列模式将并发访问变为串行访问。Redis本身没…

leetcode:合并两个有序数组

合并两个有序数组1、题目描述2、解决方案3、代码实现1、题目描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非…

mutex 锁的理解和思考

并发的影响 goroutine 并发对数据做读写操作&#xff0c;如果没有锁的保护&#xff0c;得到的结果也就是不确定的。我们通过 goroutine 做累加的例子来看一下&#xff0c;下面的情况&#xff0c;我们预期进行了10次循环&#xff0c;每次加1&#xff0c;但执行的结果却不一定的…

离开外包之后,花了10000小时,最后我走进字节跳动拿到了offer

前言&#xff1a; 没有绝对的天才&#xff0c;只有持续不断的付出。对于我们每一个平凡人来说&#xff0c;改变命运只能依靠努力幸运&#xff0c;但如果你不够幸运&#xff0c;那就只能拉高努力的占比。 2020年7月&#xff0c;我有幸成为了字节跳动的一名Java后端开发&#x…

全志A33移植openharmony3.1标准系统之添加产品编译

想玩openharmony很久了,苦于没有合适的板子能让我玩,已经适配好的开发板可真是太贵了啊,所以还是得自己动手啊,毕竟还能深入了解openharmony系统,之前有在A33上把主线uboot和主线内核跑起来,而且drm也是可以正常显示了,现在就基于此将openharmony移植到开发板上。 首先在…

【服务器】基本概念

服务器 文章目录服务器1.概览1.1.本质: **数据接受&传递**, **数据存储**, **数据处理**1.2.种类1.3.单位:1.4.标准1.5.扩展1.6.逻辑架构1.7.缓存Cache:1.8.内存DIMM1.9.DDR1.10.硬盘ref1.概览 1.1.本质: 数据接受&传递, 数据存储, 数据处理 1.2.种类 按应用分类WWW…

Snowflake Decoded基础教程

Snowflake Decoded基础教程 掌握基本的Snowflake概念并获得必要的技能以开始实施基于Snowflake的解决方案 应用程序 课程英文名&#xff1a;Snowflake Decoded Fundamentals and hands on Training 此视频教程共10.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印…