25.Linux静态动态库全解析

🔥keyipatience:个人主页

🎬作者简介:C/C++后端开发学习者

🌟专栏传送门:《c++》《linux》《c语言》

⭐️patience is key in life

硬链接补充

1.目录可以建立软链接不能建立硬链接,只有普通文件才能建立硬链接

前面我们说过软链接(ln -s不存实际数据,只保存目标路径字符串

  • 硬链接是同一个 inode 别名 ,目录硬链接会破坏目录树形结构、造成循环目录,Linux 内核直接禁止目录硬链接;
  • 软链接独立 inode,只是路径跳转,哪怕形成循环链接系统只会报找不到路径,不会破坏文件系统,因此放开目录软链接

虽然.和..本质来说也是目录的硬链接,但这是linux系统默认给当前和上级目录建立硬链接,除此之外不允许用户对目录建立硬链接

静态库

一个库的形成

.c(源代码) → .o(二进制目标文件) → 库文件(复用模块)

为什么不直接给.c 文件,而是要编译成.o 和库文件?

  • 保护源码(核心):不给别人看核心实现。.o完全是二级制根本看不懂
  • 提升效率:不用重复编译。
  • 简化使用:只给接口,不用给实现。其他人只要.h和库,在代码中include"你的头文件.h"即可使用
  • 保证稳定:防止被乱改

静态库的命名要求

.a结尾,lib开头,中间(去掉lib和.a)就是库名

方法实现

1.gcc -c xxx.c -o xxx.o

  1. 本质 :单个 C 源码编译、只编译不链接生成的二进制目标文件,包含机器码、符号表、数据段,不能直接运行。
  2. 局限 :多个.o零散文件,项目使用时每次编译都要手动罗列全部.o去链接,不方便分发

生成:ar rc 库名.a 若干.o(r-replace有更新了就替换,c-create没有就创建)

ar:归档打包命令,把多个零散.o打包成单个.a 静态库文件

eg(1):

其中:

  • -L..代表当前目录 ,告诉链接器在当前文件夹搜索库文件libmyc.a(不加 -L 时,ld只会去系统库目录/usr/lib找库,不会在当前目录查找 libmyc.a
  • -lmyc:链接libmyc.a静态库

eg(2)

  • -I路径:只作用预处理阶段(找.h 头文件) ,只在从.c编译时需要;
  • 链接.o文件时不需要-I,只需要-L(库路径)/-l(库名)

-I(i的大写)找头文件

gcc -c usercode.c -I ./lib/include(先形成.o)

gcc -o usercode usercode.o -L ./lib/mylib/ -l myc(最后和库文件链接形成可执行程序)
gcc -o usercode usercode.c -I ./lib/include -L ./lib/mylib/ - l myv(直接一次)

eg(3)

每次这样写很多太麻烦也可以直接把头文件cp到系统的头文件/usr/include下,库文件cp到/lib64下。这样再编译时就会找到相应的头文件和库文件

所以对于库的安装就是把库对应的头文件和库文件拷贝到系统的指定路径下

eg(4)makefile最终版本

动态库

•shared:表示生成共享库格式

• fPIC:产生位置无关码

• 库名规则:libxxx.so

实现方法

执行file libmyc.so

可以看到shared object说明就是动态库

对比静态库

为什么gcc已经编译好了,并且形成了可执行文件,但却不能运行,在运行时报找不到libmyc.so的错误

执行ldd usercode后,也能明确看到libmyc.so -> not found说明运行时系统找不到 libmyc.so 动态库文件,这是为什么呢?

这是因为编译链接时用-L只是编译阶段找库 ,运行时系统找不到libmyc.so动态库,gcc 编译成功≠运行时能找到动态库。

静态库为什么没有这个问题,是因为静态库在链接时就已经把库的实现拷贝到了我们的文件里,在形成了可执行程序后,就不用再依赖静态库,运行时也不用再去找了

下面介绍4中可行的方法

0.核心原则

动态库查找优先级:RPATH > LD_LIBRARY_PATH > ldconfig缓存 > 系统默认目录

1.把 libmyc.so 复制到系统默认库目录

sudo cp lib/mylib/libmyc.so /usr/lib64/

2.软链接

sudo ln -s 绝对路径 /usr/lib64/libmyc.so(软链接要绝对路径才行)

为什么这样可以

usr/lib64 里的libmyc.so不是真文件,只存一串原始文件路径(软链接文件内容存路径);系统访问到时,内核自动跳转到你存放真实 .so 的目录(软链接记录的路径下)读取文件

3.设置 LD_LIBRARY_PATH 环境变量

(1)临时设置

在这里对 export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/home/ysm/ysm3/lib/mylib,这个写法做一下分析

(1)$ = 取值符号,用来读取变量里存的数据

eg:name=abc

echo ${name}123 # 输出abc123,不加{}会识别不出变量

(2)LD_LIBRARY_PATH系统找.so动态库文件夹清单 ,里面放一堆目录,用:隔开。eg:程序运行:挨个进入 A 文件夹→找 libmyc.so→没有就进 B→再 C。(和PATH类似)

(3)${LD_LIBRARY_PATH}:先取出这个变量原来所有的旧路径 ,**防止直接赋值把系统原有库路径覆盖,**大括号分界,变量后面紧跟字母 / 数字时必用

(4):分隔符,分开前后两条路径。这里前面是旧路径,后面是新加的路径。

环境变量只能存目录,系统在目录内检索 so 文件,不能写文件名,不要写成/lib/mylib/libmyc.so

两种写法:

  • PATH=新路径:$PATH新路径放前面,优先搜索新目录
  • PATH=$PATH:新路径新路径放末尾,系统先搜原有路径,最后搜新加目录现在是后者:旧路径在前、lib 目录在后。

什么时候写绝对路径什么时候写相对路径?

  • 临时测试、不换目录:随便用相对路径(如果这里export写相对路径,cd切换到其他目录下usercode就不能正常运行)
  • 永久配置 / 软链接:一律写绝对路径(不出错)

(2)当前用户永久生效

vim ~/.bashrc

文件末尾添加export LD_LIBRARY_PATH=绝对路径:$LD_LIBRARY_PATH

立即生效source ~/.bashrc

4.永久添加自定义库目录(ldconfig 方案)

直接把库所在文件夹写入系统配置,全局永久生效

/etc/ld.so.conf.d/新建配置文件(文件名随便,后缀必须.conf)

原理:系统启动自动读取/etc/ld.so.conf.d/*.conf全部路径,每次ldconfig都会扫描这些目录入库缓存。

sudo echo "/home/绝对路径 > /etc/ld.so.conf.d/myc.conf

sudo ldconfig

怎么做到的(了解即可):

三个关键文件

  1. /etc/ld.so.conf:主配置,内部一般写 include /etc/ld.so.conf.d/*.conf
  2. /etc/ld.so.conf.d/:放自定义.conf新建 xxx.conf 写库路径(绝对路径)
  3. /etc/ld.so.cache二进制索引缓存(不能手动编辑) ,ldconfig 命令自动生成

ldconfig 做两件事

  1. 扫描目录 :读取ld.so.conf + conf.d下所有conf里写的全部文件夹,遍历里面所有lib*.so*动态库
  2. 生成缓存表 :把「库名→绝对路径」存入/etc/ld.so.cache,系统找库直接查表,不用挨个磁盘遍历文件夹(速度快)

只要路径写入配置 + sudo ldconfig刷新缓存,全系统所有终端、所有依赖该库的程序永久生效,不用每次 export

静态库和动态库都存在gcc链接时会默认用哪一个库?

答案是动态库,如果非得用静态库,就得在最后加上-static,并且在Linux下默认情况安装的大部分库都是动态库(c,c++根本就没有静态库)

eg: gcc -o usercode usercode.c -I lib/include -L lib/mylib -l myc -static

为什么文件要先形成.o文件而不直接形成可执行程序?

.o目标文件又叫可重定位目标文件

编译速度快(最主要原因)

大型项目有成百上千个文件

  • 没有 .o 文件 :改一行代码,编译器必须把所有文件从头到尾重新翻译一遍,耗时极长。
  • 有 .o 文件 :改一行代码,只重新编译这一个文件 生成新的 .o,然后把所有 .o 快速拼起来。

这就是 make 等构建工具的核心逻辑:只编译修改过的文件

eg:

代码复用(库文件)

你经常用别人写好的功能(printf)。

  • 别人不会直接说给你源代码,而是给你一堆编译好的 .o 格式的库文件.a.so)。
  • 链接时,直接把你的 .o 和库的 .o 拼在一起就行。
  • 如果没有 .o 这种中间格式,你就无法使用别人的代码,必须拿到所有人的源代码才能运行

分工解耦(模块化)

编译器只负责翻译 (把文本变成机器码),不负责拼图

  • 编译(生成 .o) :只管把单个文件翻译成二进制,不知道其他文件的存在
  • 链接(生成可执行) :负责把所有 .o 拼在一起,解决函数调用的地址问题

这种分离让编译器设计更简单

相关推荐
YOLO数据集集合1 小时前
配电站智能运维|变电一次设备识别|高压电气构件目标检测数据集|电力巡检
运维·人工智能·深度学习·yolo·目标检测·视觉检测
爱睡觉1111 小时前
在 Android 模拟器 Shell 下运行 ncnn 推理的性能排查记录
linux·shell
weixin_520649871 小时前
通信与TCP核心知识
服务器·网络·tcp/ip
开开心心_Every2 小时前
多连接方式的屏幕共享工具推荐
运维·服务器·pdf·电脑·excel·tornado·dash
AskHarries2 小时前
Workspace:文件系统、项目上下文和执行边界
java·服务器·前端
落羽的落羽2 小时前
【项目】JsonRpc框架——开发实现1(细节功能、字段定义、抽象层、具象层)
linux·服务器·网络·c++·人工智能·算法·机器学习
shixuzhimeng2 小时前
FTP服务器项目
linux·网络·ftp
Chris-zz2 小时前
Linux:线程概念与控制
linux·运维
剑神一笑2 小时前
Linux chown 命令详解:从 inode 到实战
linux·运维·服务器