Linux | 动静态库 | 动静态链接 | makefile库打包 | 第三方库使用

article/2025/10/31 2:44:30

文章目录

    • 何为动静态库
    • 库文件的链接
      • 静态链接
      • 静态库打包
      • 动态链接
      • 动态库打包
    • 第三方库的使用
      • 静态库的使用
      • 动态库的使用
    • 在系统层面上的动态链接理解

何为动静态库

静态库(.a):在程序编译链接时将静态库二进制码拷贝到程序代码中,程序运行时不再需要外部的静态库
动态库(.so):程序在编译链接完成后,进入运行时才会去链接动态库中的二进制代码,多个程序可以共享同一个动态库
动态链接:程序运行时,操作系统将动态库从磁盘加载到内存中,这个过程叫做动态链接
动态链接生成的可执行文件包含了一个表,表中存储需要使用的动态库函数地址,而不是函数的所有代码
操作系统在进程的页表中建立虚拟地址与动态库函数地址(动态库在内存中的物理地址)之间的映射,每个进程的页表都能映射内存中的动态库,所以只需要将动态库加载到内存中,该动态库就能被多个进程同时使用,比起静态链接将函数的所有代码拷贝到程序中的方式,动态链接生成的程序占用空间更少

库文件的链接

在当前目录的上级目录下有两个文件,hello.c,hello.h
在这里插入图片描述
在这里插入图片描述
两个文件是自定义函数Print的实现和声明,在当前目录下创建test.c,该程序包含了Print函数的头文件,并且使用上级目录的自定义函数Print
在这里插入图片描述
编译该文件,但是系统报错,很显然,虽然程序有Print函数的声明(引用了上级目录的头文件…/hello.h,如果引用方式为hello.h程序报的错将是不明确的符号Print,找不到头文件hello.h,系统都不知道Print是个啥),但是没有Print的具体实现(报错信息为未定义引用的Print)。可以将Print看成一个库中的函数,除了包含头文件之外,要使用这个函数需要将目标文件与库文件进行链接,至于说为什么我们使用printf,scanf这样的库函数不需要我们链接,只需要包含对应的头文件,因为系统默认链接了这些标准库。当我们要使用第三方库时,就需要手动链接库文件

链接库文件则分为静态链接和动态链接两种方式

静态链接

先说明一个问题,对于刚才的代码,将Print函数的源文件hello.c编译生成obj目标文件,该目标文件能不能与test.c生成的目标文件test.o 一起编译生成可执行程序,如果能生成,该可执行程序能不能正确的运行?
在这里插入图片描述
编译两个源文件后,当前目录生成了两个obj目标文件
在这里插入图片描述
将两个目标文件编译生成可执行文件test并运行,可以看到Print函数被成功执行

综上所述,只要有了由需要使用函数的源文件生成的obj文件(obj文件是翻译汇编语言生成的二进制机器文件),将obj文件与main函数所在的obj文件一起编译,生成的可执行程序就能调用所需要的函数。所以,当我们需要使用其他人定义的函数时,可以将函数所在的源文件进行编译,生成obj文件,当然,如果只有obj文件也是可以的,最后只需要将obj文件一起编译就能生成可执行文件,成功使用其他人定义的函数。

而所谓的静态链接就是将库中的obj文件与main函数所在的obj文件一起编译生成可执行文件的过程,执行静态链接后,因为将库的obj文件与自己的文件一起编译,所以生成的可执行文件会包含库文件的代码,占用较大的内存。

把多个源文件编译生成的obj文件打包,并存储在一个文件下,这个文件就叫做静态库

静态库打包

使用ar指令归档需要的obj文件生成静态库,归档后生成的文件可以检索源obj文件信息。
在这里插入图片描述

具体指令为ar -rc 归档文件 原始文件

在归档文件名相同的情况下,r选项将新的归档文件替代旧的归档文件
在这里插入图片描述
当归档文件不存在时,使用c选项创建该归档文件
在这里插入图片描述
我将创建静态库的操作放在makefile文件中,以下是makefile文件的内容
在这里插入图片描述
库文件以lib开头,所以我们创建的归档文件名为libmyfirst.a,a后缀表示这是一个静态库,mymath_s.o和hello_s.o是需要进行归档的原始文件,原始文件是经过源文件的编译生成的二进制文件。

该目录此时只有mymath.c和hello.c这两个源文件,要得到obj目标文件需要使用gcc -c指令编译源文件。

静态库归档完成后,还需要头文件声明obj文件中的函数(使用一个静态库不仅需要库函数,还需要头文件告知编译器库函数的存在),所以定义一个伪目标static,该目标的依赖方法将创建两个目录,一个存放库文件,一个存放头文件,至此一个静态库文件就能向外发布了在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
用tree指令打印lib-static的目录结构,可以看到头文件和库文件都被打包放入对应的目录下了。

动态链接

和静态库的原理相同,动态库也需要生成目标文件obj,区别就是动态库的obj文件不用拷贝到可执行文件中,系统会将其加载到内存中,可执行文件只需要知道动态库在内存的地址(函数入口地址),在程序运行时,可以通过该地址调用内存中的动态库函数。

动态库打包

打包动态库需要的obj文件与打包静态库需要的obj文件不同,因为静态链接将库文件拷贝到可执行文件中,库文件的代码会在进程地址空间呈现,在把源文件编译为obj文件时,编译器将代码的链接地址写死,即采用绝对地址的方式,当程序运行时,这些obj文件最后生成的可执行文件,只能在写死的链接地址上才能正确运行代码,也就是说,运行地址必须和链接地址相同,程序才可以正常运行。但对于动态库来说,一个动态库加载到内存后,可以被多个进程链接,每个进程共享区(存储动态库的虚拟地址的区域)存储的地址都可能不相同,即运行地址可能发生变化,所以动态库在编译obj文件时,不能采用将链接代码写死的方式,否则程序无法正确运行,应该采用相对代码的方式编译源文件,不将链接地址写死。

所以,对于动态库的源文件需要使用位置无关的方式编译,对于静态库的文件使用位置有关的方式编译即可。

gcc -fPIC -c生成的obj文件为位置无关文件,也就是刚才说的采用相对代码的方式编译,
gcc -shared 源obj文件 动态库文件,该指令可以将obj文件打包成一个动态库

在这里插入图片描述

第三方库的使用

静态库的使用

可以看到我在11_16这个目录下生成了静态库和动态库,接着退出这个目录,随意进入一个目录,在该目录下创建一个源文件,引用hello.h和mymath.h这两个头文件,然后调用Print和myPow函数(两函数分别在两头文件中声明)在这里插入图片描述
gcc编译该源文件,我们发现编译器不认识hello.h这个头文件(当然mymath.h也不认识,只是hello.h在前,不认识hello.h后直接报错,后面的错误没有显示)
在这里插入图片描述
很显然系统不知道有hello.h和mymath.h这两个头文件。系统编译程序时,默认会去/usr/include这个目录下查找头文件是否存在,如果不存在,系统报错,很显然我们自定义的头文件没有在这个目录下,我们只需要将我们的头文件添加到这个目录下就行了。
在这里插入图片描述
此时再次编译源文件
在这里插入图片描述
程序还是报错,虽然程序认识Print和myPow这两个函数,但程序却找不到这两个函数的定义。显然,程序在编译时知道有Print和myPow是两个函数,但是却不知道函数的地址,因为在之后的链接过程中,函数的地址会被写入,所以编译阶段就将函数地址放空,函数地址处于一个待写入的状态,接着编译完成。但是链接时找不到这两个函数的地址,因为在链接的库中,系统找不到哪个库中含有Print和myPow这两个函数。所以目前我们可知的是,包含Print和myPow 这两个函数的库没有被链接,在链接时,我们需要告知编译器库的路径(库在哪?),以及要链接哪个库(平常使用的标准库都存储在usr/lib64这个目录下,编译器会在该默认路径下链接你需要的标准库,所有使用第三方库时,我们需要告知编译器要链接哪个库,以及库所在的路径)。

gcc -l 要链接的库名称

在这里插入图片描述
将库文件和头文件都拷贝到系统的默认路径下,就不用告知编译器头文件和库文件的路径,因为编译器会区默认路径下查找。所以我们只需要告知编译器要链接的库名称。
在这里插入图片描述
注意,链接库文件时,库文件名不用加前缀lib和后缀.a或者.so,编译器会根据命名规范自动补足名称,然后查找对应的库。在拷贝自定义的库文件和头文件到系统默认路径下后,编译源文件时指定要链接的库就能成功编译。
在这里插入图片描述
但是将自己的库文件和头文件安装到系统的默认路径下是一种不好的行为,会污染系统的库文件,除了将库文件和头文件安装到系统默认路径下,还能通过添加gcc编译时的选项使用第三方库。

gcc -L 库文件查找的路径
gcc -I 头文件查找的路径

刚才我们的编译只是指定了要链接的库,没有指定库文件和头文件的路径,因为编译器的默认行为,我们不需要特别指定路径。当我们需要链接的库不在系统默认路径下,就需要我们告知编译器库文件和头文件所在的路径。所以只要我们有静态库,并且知道库文件和头文件的路径,我们就能通过添加gcc的选项,指明对应文件路径的方式,编译一个使用第三方库的程序。
在这里插入图片描述

动态库的使用

动态库的使用也是两种方式,第一种是简单粗暴的在系统默认路径下添加库文件和头文件,最后在编译时,指定要链接的库,方法和静态库一样,这里不再阐述。

第二种方式也与静态库一样:添加编译选项,虽然可以生成可执行文件,但可执行文件却无法执行。
在这里插入图片描述
原因是程序运行时需要链接库文件,却找不到对应库文件,为什么静态库不存在这样的问题?静态链接编译生成的可执行程序将整个库都拷贝到可执行文件中,运行时不再需要静态库,而动态链接只是拷贝了一张地址表,在运行时需要通过表中地址查找并调用动态库中的函数,但现在的情况是:可执行程序知道要链接哪个动态库,却不知道动态库的路径(在磁盘中的位置),也就无法链接

在这里插入图片描述
在这里插入图片描述
解决无法链接动态库有三种方法,一是向环境变量LD_LIBRARY_PATH导入动态库路径,程序运行时,除了会在系统默认路径下查找需要链接的库,还会在该环境变量存储的路径下查找。但这种方式是一次性的,重启系统后,环境变量将会变为默认值。

另一种方式可以永久解决次问题:修改系统配置文件,/etc/ld.so.conf.d/目录下存储了许多配置文件,每个配置文件存储了一个路径,如果系统中使用了第三方库,那么程序在动态链接时,除了查找系统默认路径下的库文件,还会到/etc/ld.so.conf.d/路径下的配置文件存储的路径下查找库文件。

在/etc/ld.so.conf.d/路径下添加配置文件mylib.conf,向文件写入自定义库文件所在路径
在这里插入图片描述

然后执行sudo ldconfig,使新的配置文件生效
在这里插入图片描述

最后一个方法,与第一种简单粗暴的向系统默认目录添加文件类似,都是向系统默认路径添加文件,只是这次添加的是库文件的软链接
在这里插入图片描述
进行链接的原文件最好是绝对路径,程序编译时,只需要告知需要的头文件路径以及要链接的库名称即可,编译生成的可执行文件不会出现找不到库的错误,因为系统会去默认库文件路径下查找需要链接的库是否存在。

在系统层面上的动态链接理解

在这里插入图片描述
最开始时,可执行程序与其需要链接的动态库都存储在磁盘中,执行可执行程序后,操作系统将可执行程序与动态库从磁盘加载到内存,并建立task_struct描述可执行程序,task_struct中有一个mm_struct,不仅存储了进程的虚拟地址空间,还存储了映射虚拟地址空间与物理地址空间的页表。在虚拟地址空间中,堆区向上增长,栈区向下增长,两者相对而生,中间有一块共享区,该共享区存储的就是动态库函数的虚拟地址,动态库由磁盘加载到内存中,在物理空间上有属于自己的地址空间,页表将建立共享区与动态库的物理地址之间的映射。所以共享区存储了动态库函数的虚拟地址,程序在代码区执行代码时,如果遇到了动态库函数,会通过函数地址(虚拟地址)跳转到共享区,由于地址是虚拟地址,进程将通过页表的映射得到动态库函数的物理地址,进程将执行处于内存上的动态库函数,执行完毕,再从共享区跳转回代码区。

所以,当一个进程需要动态链接时,需要将动态库从磁盘加载到内存中,对于第三方库,如果系统不知道库文件在磁盘的哪个位置(不知道库文件路径),那么系统就无法找到库文件,更不用说将其加载到内存中,程序也就无法与库进行链接。所以对于第三方库文件没有存储在系统默认路径下的情况,可执行程序虽然知道要链接哪个库文件,但不知道库文件的位置,也就无法加载库文件,程序中的库函数没有映射对象,函数只有声明,没有定义,当然无法调用函数


http://chatgpt.dhexx.cn/article/e02G36qI.shtml

相关文章

覆盖libc.so.6的惨痛教训

覆盖libc.so.6的惨痛教训 背景问题原因解决1、当前session未断开2、OS崩溃重启,所有ssh session断开 惨痛教训1、对于上产环境的内核依赖库文件不能随意覆盖、删除。2、 scp 文件覆盖问题 总结参考 背景 发生时间: 2022年11月28日08:55:20 偷了个懒,在安…

【Linux】自动生成makefile(ubuntu)

文章目录 前言一、Automake工具二、具体步骤1.下载Automake2.autoscan3.重命名configure.scan为configure.ac4.修改重命名后的configure.ac5.执行aclocal命令6.autoheader7.autoconf8.创建Makefile.am9.automake10.执行configure11.执行make12.执行Makefile编译完生成的可执行文…

【Linux】基础IO —— 下(实现动静态库)

🎇Linux:基础IO 博客主页:一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限,出现错误希望大家不吝赐教分享给大家一句我很喜欢的话: 看似不起波澜的日复一日,一定会在某一天让你看…

【Linux】第九章 动态库和静态库(生成原理+生成和使用+动态链接)

🏆个人主页:企鹅不叫的博客 ​ 🌈专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接:代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

Linux——自动化编译(make的使用)、库文件(静态库和共享库的使用)

一、make的使用(自动化编译) make是Linux上的工程管理工具,可以实现自动化编译;make可以提高我们的编译效率。 1.安装make 命令:sudo apt install make 2.make的用法 (1)创建makefile文件&am…

rpc系列-ZooKeeper

一.简介 Zookeeper是一个分布式协调服务,就是为用户的分布式应用程序提供协调服务。 Zookeeper本身就是一个分布式程序(只要有半数以上节点存活,zk就能正常服务)。 Zookeeper所提供的服务涵盖:主从协调、服务器节点…

HBase 一文读懂

本文基于《尚硅谷大数据技术之HBase》编写。 HBase 简介 HBase定义 HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。 HBase数据模型 HBase的数据模型同关系型数据库(RDMS)很类似,数据存储在一张表中,有行有列。但从H…

Zookeeper学习

文章目录 今日目标apache zookeeperzookeeper的概念分布式和集群的理解zookeeper的集群架构和角色zookeeper的5大特性部署zookeeper集群环境计算机集群的安装部署三台节点(克隆两台)配置节点内存和CPUzookeeper安装 zookeeper的数据模型zookeeper的节点类…

Redission和Zookeeper分别实现分布式锁

Redission和Zookeeper分别实现分布式锁(windows) 1、Redission实现分布式事务 1.1 前提准备 下载好nginx(windows版本)下载好Jmeter(模仿高并发)下载好redis(windows版) 1.2 代码…

Zookeeper 客户端之基本操作指令

ZooKeeper命令行工具类似于Linux的shell环境,不过功能肯定不及shell啦,但是使用它我们可以简单的对ZooKeeper进行访问,数据创建,数据修改等操作. 命令行工具的一些简单操作如下: zkCli.sh客户端连接命令 ls 与 ls2 命…

kafka内置zookeeper启动失败报错INFO ZooKeeper audit is disabled. (org.apache.zookeeper.audit.ZKAuditProvider)

kafka内置zookeeper启动失败报错INFO ZooKeeper audit is disabled.(org.apache.zookeeper.audit.ZKAuditProvider)2022年新版win10安装kafka 安装配置kafka,在启动zookeeper时报错ZooKeeper audit is disabled 原因分析: 寻找资料发现是zookeeper设置参…

zookeeper日志及快照清理操作

事务日志可视化转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/bin/sh # scriptname: zkLog2txt.sh # zookeeper事务日志为二进制格式,使用LogFormatter方法转换为可阅读的日志 if [ -z "$1" -o "$1" "-h&quo…

【RPC】注册中心实现方案之ZooKeeper

文章目录 ZooKeeper一致性协议:ZAB ZooKeeper ZooKeeper是一个开源的分布式协调服务,它可以用来协调和同步多服务器之间的状态。 ZooKeeper 可以作为微服务架构中注册中心的选型,它最需要被关心的也是数据模型和一致性协议。数据模型关乎服…

Linux 搭建zookpeer集群和配置

zookpeer和JDK1.8下载地址 下载地址:zookpeer和jdk1.8 提取码:w189 解压以及配置zookpeer tar -zxvf zookeeper-3.4.6.tar.gz tar -zxvf jdk-8u144-linux-x64.tar.gz 基本参数配置 参数描述clientPort主要定义客户端连接zookeeper server的端口&…

hadoop-zookeeper的详细介绍以及安装配置步骤

一、zookeeper的介绍 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、…

k8s-7: kafka+zookeeper的单节点与集群的持久化

之前在k8s环境中有这个需求&#xff0c;看了好多的文档&#xff0c;都有坑&#xff0c;踩了一边总结一下&#xff0c;需要的朋友可自取 一、单节点部署 建议开发环境使用&#xff0c;且此处采用动态挂载的&#xff0c;生产不建议 1、安装zk cat > zk.yaml <<EOF a…

Docker 安装Zookeeper

第一步&#xff1a;查看本地镜像和检索拉取Zookeeper 镜像 # 查看本地镜像 docker images # 检索ZooKeeper 镜像 docker search zookeeper # 拉取ZooKeeper镜像最新版本 docker pull zookeeper:latest [rootlocalhost ~]# docker images REPOSITORY TAG …

mac pro m1:搭建zookeeper集群并设置开机自启

0. 引言 之前我们讲解过搭建zookeeper单节点&#xff0c;但在实际生产中&#xff0c;为了保证服务高可用&#xff0c;通常我们是采用集群模式。所以本次我们来实操集群模式的搭建 1. zk集群模式 zk可以作为注册中心和配置中心&#xff0c;常用在微服务各类组件的多节点服务治…

一款实用的数据恢复软件—zook data recovery wizard

zook data recovery wizard是RecoveryTools下的一款子品牌&#xff0c;同时也是一款功能实用的数据恢复软件&#xff0c;该软件可以从Windows中恢复已删除&#xff0c;损坏&#xff0c;格式化和丢失的数据&#xff0c;能够支持从驱动器&#xff0c;SD卡&#xff0c;硬盘&#x…

zook 报错 Unable to read additional data from server sessionid 0x0

zook报错启动报错&#xff1a; 2017-09-25 18:33:46,913 - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread1183] - Unable to read additional data from server sessionid 0x0, likely server has closed socket, closing socket connection and attempting r…