Linux中动静态库的理解

软硬链接

硬链接

ln a b,就是将目标文件a硬链接到文件b

ls -i 查看文件的inode,ls -li查看所有文件的inode

原理

Linux 文件由inode + 数据块组成:

  • inode:记录文件元信息(权限、大小、指向数据块指针),文件名只是 inode 的一个别名。
  • 硬链接本质:多个文件名指向同一个 inode

特点

  1. 同一个 inode 号,多个文件名,共享磁盘数据
  2. 只能对文件创建,不能给目录创建硬链接(系统目录本身有. .. 硬链接除外)
  3. 不能跨文件系统(分区),inode 只在当前文件系统有效
  4. 删除一个硬链接,只是删掉一个文件名;只要还有一个链接存在,文件数据不会删除
  5. 修改任意一个硬链接文件,所有链接文件都会同步变化(共享同一份数据)
  6. 不额外占用磁盘空间(仅增加目录项记录)

创建并写入内容到test.txt,观察inode

软连接

ln -s a b,将文件a软链接到文件b

test.txt 和 hard.txt inode 相同;soft.txt 是独立 inode

原理

软链接本身是独立的小文件 ,它的 inode 指向的数据内容保存的是源文件路径。类似 Windows 的快捷方式。

特点

  1. 软链接有自己独立的 inode,是一个单独文件
  2. 可以链接文件,也可以链接目录
  3. 支持跨分区、跨文件系统
  4. 如果源文件删除 ,软链接依然存在,但变成失效链接(红闪烁),访问会提示 No such file
  5. 修改软链接对应的文件,实际修改的是源文件数据
  6. 软链接文件本身占用少量磁盘空间(存放路径字符串)
  7. 相对路径创建软链接时,源路径是相对于软链接所在目录,不是当前执行命令目录,很容易踩坑

使用场景

  • ✅ 硬链接:重要文件多备份,防止误删(同一分区内)
  • ✅ 软链接:版本切换(python 多版本)、目录迁移、快捷访问、跨分区链接

|-------|--------|------|---|---|---|
| 特性 | 硬链接 | 软链接 | | | |
| inode | 和源文件相同 | 独立 inode || | |
| 本质 | 文件名别名 | 保存路径的独立文件 || | |
| 支持目录 | ❌ 不支持 | ✅ 支持 | | | |
| 跨分区 | ❌ 不支持 | ✅ 支持 | | | |
| 删除源文件 | 文件还在 | 链接失效 | | | |
| 文件大小 | 和源一致 | 路径字符串长度 || | |
| 权限 | 和源一致 | 软链接权限几乎无效,以源文件为准 ||||

动静态库

动态库

库:库是写好的现有的,成熟的,可以直接复用的代码实现,不需要去写代码时需要应用到然后在去重写代码,直接调用库中的代码应用就可以。

动态库(.so)

原理:链接阶段只记录库的符号信息,不拷贝代码;程序运行时才加载动态库到内存。多个进程可以共享内存里同一份库代码。

特点:

✅ 优点

  1. 可执行文件小,内存中只加载一份库,节省内存
  2. 库升级:替换 .so 文件,程序不用重新编译(接口不变即可)

❌ 缺点

  1. 运行环境必须存在对应 .so,否则程序无法启动(经典:error while loading shared libraries)
  2. 运行时多一步加载,速度稍慢
cpp 复制代码
# 1. 生成位置无关代码 -fPIC(必须!)
gcc -c -fPIC add.c -o add.o

# 2. 生成动态库 libxxx.so
gcc -shared add.o -o libadd.so

# 3. 编译程序链接动态库
gcc main.c -o main -L. -ladd

创建及使用

add.h

cpp 复制代码
#ifndef __ADD_H__
#define __ADD_H__
int add(int a,int b);
#endif

add.c

cpp 复制代码
#include "add.h"
int add(int a,int b)
{
    return a+b;
}

main.c

cpp 复制代码
#include <stdio.h>
#include "add.h"
int main(void)
{
    printf("10+20=%d\n", add(10,20));
    return 0;
}
第 1 步:生成位置无关代码 -fPIC
复制代码
gcc -c -fPIC add.c -o add.o
  • -c:只编译,不链接,生成目标文件 add.o
  • -fPIC:位置无关代码,动态库必须加这个参数!

动态库会被加载到内存任意地址,代码不能写死内存地址,所以要 PIC。不加这个编译会报错!

第 2 步:用 add.o 打包生成动态库 libadd.so
复制代码
gcc -shared add.o -o libadd.so
  • -shared:告诉 gcc 生成共享库(动态库)
  • 命名规则:libxxx.so,xxx 是库名,后面 -lxxx 使用

执行完当前目录就有 libadd.so

③ 3-2 动态库使用(编译 main 程序链接动态库)
复制代码
gcc main.c -o main_dynamic -L. -ladd

解释:

  • -L.:编译时,在当前目录找库文件(libadd.so)
  • -ladd:链接 libadd.so(自动省略 lib 和 .so)

✅ 到这里,编译成功,生成可执行文件 main_dynamic

问题

现在直接运行:

复制代码
./main_dynamic

报错:error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory

❗核心概念区分(很多人搞不懂) -L. 只是编译阶段 给 gcc 看的:编译的时候去当前目录找库 程序运行阶段,操作系统的动态链接器 ld-linux.so 不会默认去当前目录找 so! 编译成功 ≠ 运行成功!

用 ldd 查看程序依赖的动态库:

复制代码
ldd ./main_dynamic

你会看到:libadd.so => not found

4 种解决方案(由简单到永久)
方案 1:临时环境变量 LD_LIBRARY_PATH(课堂测试首选,临时生效)
复制代码
# 把当前目录加入动态库搜索路径
export LD_LIBRARY_PATH=$PWD
# 再运行程序
./main_dynamic

✅ 输出:10+20=30

缺点:只在当前终端有效,新开终端就失效。

方案 2:编译时写入 rpath(推荐实验用,把路径写进程序内部)

重新编译 main,加上 -Wl,-rpath=$PWD

复制代码
gcc main.c -o main_dynamic -L. -ladd -Wl,-rpath=$PWD
  • -Wl,-rpath:传递参数给链接器,把库路径硬编码到程序中 之后直接运行 ./main_dynamic,不用设置环境变量。
方案 3:修改系统配置文件(全局永久)
  1. 将库路径写入 /etc/ld.so.conf

    sudo echo "/root/lesson10" >> /etc/ld.so.conf

  2. 更新系统动态库缓存

    sudo ldconfig

所有用户、所有终端都可以找到这个 so,永久生效。

方案 4:直接拷贝 so 到系统库目录
复制代码
sudo cp libadd.so /usr/lib

系统默认搜索 /usr/lib,拷贝完成即可运行。

静态库

原理

编译链接阶段,把库里面的代码完整复制,合并进可执行文件。运行时不再依赖原库文件。

特点

✅ 优点

  1. 运行独立,不需要系统上存在该库,移植方便
  2. 运行速度略快(运行时不用加载库)

❌ 缺点

  1. 可执行文件体积大,多个程序用同一个静态库,会有多份副本,浪费磁盘
  2. 库更新:必须重新编译链接程序才能生效
cpp 复制代码
# 1. 源码编译成目标文件 .o
gcc -c add.c -o add.o

# 2. ar 打包成静态库 libxxx.a
ar rcs libadd.a add.o
# r:插入文件 c:创建库 s:建立索引

# 3. 使用静态库编译程序
gcc main.c -o main -L. -ladd
# -L 指定库搜索路径;-ladd 链接 libadd.a(省略lib和.a)

创建使用静态库

add.h

cpp 复制代码
#ifndef __ADD_H__
#define __ADD_H__
int add(int a, int b);
#endif

add.c

cpp 复制代码
#include "add.h"
int add(int a, int b)
{
    return a + b;
}

main.c

cpp 复制代码
#include <stdio.h>
#include "add.h"
int main(void)
{
    int a = 10;
    int b = 20;
    printf("10 + 20 = %d\n", add(10,20));
    return 0;
}
静态库生成
  1. 把 add.c 编译成目标文件 add.o

    gcc -c add.c -o add.o

  2. 使用 ar 工具打包,生成静态库 libadd.a

    ar rcs libadd.a add.o

  • r:插入 / 替换文件
  • c:创建库文件
  • s:生成符号索引,加快链接

编译 main,链接静态库:

复制代码
gcc main.c -o main_static -L. -ladd
  • -L.:告诉编译器,库文件在当前目录查找
  • -ladd:链接 libadd.a,自动补全 lib 和 .a

运行:

复制代码
./main_static

输出:10 + 20 = 30

✅ 静态库特点:编译完成后,删除 libadd.a,程序依旧可以正常运行。代码已经拷贝进可执行文件。

|------|---------|-----|---|
| 项目 | 静态库 .a | 动态库 .so ||
| 链接时机 | 编译链接期 | 运行期 | |
| 代码拷贝 | 完整复制进程序 | 只记录符号,不复制 ||
| 文件体积 | 大 | 小 | |
| 内存占用 | 多进程多副本 | 内存共享一份 ||
| 更新库 | 重新编译程序 | 替换 so 即可 ||
| 运行依赖 | 不依赖原库 | 必须存在 so 文件 ||
| 扩展名 | .a | .so | |

面试题

一、基础简答题(必背)

1. 什么是静态库?后缀?原理?

答: 静态库:.a(Linux),本质是一堆 .o 目标文件的归档包(ar打包)。 编译链接阶段 ,静态库里面的代码会直接复制拷贝到你的可执行程序中。 程序运行时不再依赖静态库文件。

优点:运行独立,不需要外部库;部署简单。 缺点:可执行文件体积大;多个程序同时使用会有多份代码副本,浪费内存;库更新后程序必须重新编译。

2. 什么是动态库(共享库)?后缀?原理?

答: 动态库:.so(Linux)。 链接阶段只记录符号信息,不拷贝代码 ;等到程序运行的时候,操作系统才把动态库加载进内存,多个进程可以共享同一份库代码。

优点:程序体积小;内存节省;库升级替换,程序不用重新编译。 缺点:运行时依赖 so 文件,缺少就无法运行;部署要带上库。

3. 静态库和动态库的区别(高频面试大题)

答:

  1. 链接时机不同 静态库:编译链接阶段,代码拷贝进可执行文件。 动态库:编译只做符号检查;运行时才加载库。
  2. 文件依赖 静态:编译完成后,.a库文件可以删除,程序正常运行。 动态:运行时必须存在 .so,否则程序启动失败。
  3. 内存占用 静态:多个进程运行,内存有多份库代码。 动态:内存中只保留一份库,多个进程共享。
  4. 程序大小 静态编译出来的可执行文件更大;动态更小。
  5. 更新方式 静态:库修改,应用必须重新编译。 动态:替换 so 文件,应用不用重新编译。
4. 静态库怎么生成、使用?

答:

复制代码
#1.编译生成目标文件
gcc -c add.c -o add.o
#2.ar打包生成静态库libadd.a
ar rcs libadd.a add.o
#3.使用静态库编译程序
gcc main.c -o main_static -L. -ladd
  • -L.:编译时库搜索路径
  • -ladd:链接 libadd 库
5. 动态库怎么生成、使用?为什么要 -fPIC?

答:

复制代码
#1.生成位置无关代码
gcc -c -fPIC add.c -o add.o
#2.生成动态库
gcc -shared add.o -o libadd.so
#3.链接程序
gcc main.c -o main_dynamic -L. -ladd

-fPIC:Position Independent Code,位置无关代码。 动态库可以被加载到内存任意地址,代码不能写死绝对内存地址,必须生成 PIC 代码;静态库不需要。

原理 & 坑点题(你实验踩过的坑,面试最爱问)

6. 编译时 -L 和环境变量 LD_LIBRARY_PATH 的区别?

答:

  • -L:编译链接阶段生效,告诉 gcc 去哪里找库文件。
  • LD_LIBRARY_PATH:程序运行阶段生效,告诉操作系统动态链接器去哪里找 so。

所以:编译成功 ≠ 运行成功。

7. 动态库编译成功,运行报错 cannot open shared object file,原因 + 解决方案?

答: 原因:编译时 -L 指定路径只给编译器用;运行时动态链接器不会默认去当前目录找 so。

四种解决办法:

  1. 临时设置环境变量:export LD_LIBRARY_PATH=$PWD(当前终端生效)
  2. 编译加 -Wl,-rpath,路径,把库路径写入程序内部
  3. 修改 /etc/ld.so.conf 添加库路径,执行 sudo ldconfig 更新缓存(全局永久)
  4. 将 so 拷贝到系统默认库目录 /usr/lib
8. 同一个目录下同时存在 libadd.a 和 libadd.so,执行 gcc main.c -L. -ladd,会链接静态还是动态库?怎么强制静态链接?

答: gcc 优先链接动态库 libadd.so 。 强制静态链接:增加 -static 参数

复制代码
gcc main.c -o main_static -L. -ladd -static
9. ldd 命令作用?

答: ldd 可执行文件名,查看这个程序依赖哪些动态库,以及库是否能找到。找不到会显示 not found。

10. ar 命令 rcs 参数含义?

答:

  • r:替换 / 插入文件到库中
  • c:创建库文件
  • s:生成符号索引,加快链接速度
11. 动态链接的两种方式:隐式链接、显式链接(dlopen)

答: 隐式链接:就是我们刚才实验这种,编译时 -lxxx 链接库,程序启动自动加载 so。 显式链接:代码里面用 dlopen()、dlsym()、dlclose() 在代码里手动打开动态库,运行时随时加载,不需要编译时链接。插件化框架常用。

12. 动态库的 rpath 是什么?

答: rpath 是写入可执行程序内部的库搜索路径。程序运行时优先从 rpath 指定目录查找 so,不需要配置环境变量。编译参数:-Wl,-rpath=/xxx/path

相关推荐
分布式存储与RustFS1 小时前
模型 checkpoint 放对象存储:分片上传、断点续传与残留清理
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
M78佐菲2 小时前
ARM学习笔记(11)
linux·arm开发·笔记·嵌入式硬件·学习
M78佐菲2 小时前
ARM学习笔记(10)
linux·arm开发·笔记·嵌入式硬件·学习
半仙白桑2 小时前
内核篇第十五讲:system-V共享内存
linux·共享内存·shmget·shmat·shmdt·shmctl
一条破秋裤3 小时前
Linux 线程创建:pthread_create 与基本回收
java·linux·运维
可涵不会debug3 小时前
第一次学 LangGraph:用一个快递案例搞懂 State、Node、Edge 和 compile
服务器·数据库·mysql
s6516654964 小时前
IDT和GDT是什么
linux
Android系统攻城狮4 小时前
Linux Gstreamer深度解析之gst_audio_decoder_set_estimate_rate调用流程与实战(四十五)
linux·gstreamer音视频·音视频进阶
Web极客码4 小时前
抹平首包开销:我用 Claude 调优 Nginx 的 Brotli 与 Gzip 动态压缩,平衡 CPU 与网络吞吐
服务器·agent 工作流·agent 图