
观众老爷们大家好 这里是邪修KING的独家频道 本文属于系列Linux系统篇 ------操作指令 一起学Linux的小伙伴可订阅专栏: Linux系统篇 环境:Ubuntu + gcc;以 C 语言为例子,面试核心考点全覆盖
先记住核心一句话:
库 = 一堆编译好的 .o 目标文件打包而成,方便别人调用函数,不用给源码。
一、前置基础:gcc 编译四阶段(链接发生在第 4 步)
预处理 :gcc -E → .i
编译 :gcc -S → .s汇编
汇编 :gcc -c → .o 目标文件(二进制,未链接)
链接 :gcc 默认自动执行 → 可执行程序
.o 文件只有代码,缺少函数地址;链接就是把多个.o + 库整合,补全地址,生成可执行文件。
二、两种库基础概念
静态库 (Static Library)
后缀:.a(Linux)
本质:多个 .o 文件用 ar 工具打包归档
链接时机:编译链接 阶段
动态库 (共享库 Shared Library)
后缀:.so(Linux;windows dll,mac dylib)
本质:特殊格式的 ELF 二进制文件
链接时机:编译阶段只是标记依赖,运行时加载
第一部分:静态库 .a
1. 制作静态库步骤
假设有 func.c 提供函数
bash
#1. 先编译生成目标文件
gcc -c func.c -o func.o
#2. ar打包生成静态库 libfunc.a
ar rcs libfunc.a func.o
库文件名强制规范:libxxx.a
链接时使用 -lxxx,gcc 自动补前后缀。
2. 使用静态库编译
main.c 调用 func 里的函数
bash
gcc main.c -o main -L. -lfunc
参数解释:
-L.:库搜索路径,.代表当前目录
-lfunc:使用库 libfunc.a
3. 静态链接原理【重中之重】
链接器把库中你用到的 .o 代码,直接拷贝一份,塞进最终可执行文件!
内存示意图:
可执行文件main
├ main.o 代码
└ func.o【从libfunc.a复制过来的完整机器码】
静态库特点
优点
1.运行程序不再依赖原库文件!拿到 main 程序直接运行,不需要 libfunc.a
2.运行速度略快(运行时不用加载库)
缺点
1.磁盘空间浪费:多个程序使用同一个库,每个程序都会复制一份代码,体积膨胀
2.库升级麻烦:如果 libfunc.a 更新,所有程序必须重新编译链接,否则依旧使用旧代码
4. 经典坑
静态库只拷贝被调用的目标文件,不是把整个.a 全部塞进去。
第二部分:动态库 .so(共享库)
1. 制作动态库
bash
# -fPIC 生成位置无关代码【动态库必备参数!必考】
gcc -c -fPIC func.c -o func.o
# 生成动态库 libfunc.so
gcc -shared func.o -o libfunc.so
-fPIC:Position Independent Code
代码没有硬编码地址,支持在内存任意位置加载,动态库强制需要。
-shared:生成共享库
2. 使用动态库编译
bash
gcc main.c -o main -L. -lfunc
编译命令看起来和静态库一模一样!gcc 默认优先寻找动态库。
如果想强制选用静态库:gcc main.c -o main -L. -lfunc -static
3. 动态链接原理(核心难点)
编译阶段:不会拷贝库代码进程序!
仅仅在可执行文件里写下一条记录:「运行时需要加载 libfunc.so」
运行流程:
1.执行 ./main
2.操作系统动态链接器 ld-linux.so 启动
3.根据程序记录,去系统路径寻找 libfunc.so
4.将 so 加载进内存,解析函数符号,修正地址
5.程序正常运行
内存特征:
多个程序运行时,可以共享内存里同一份 .so!
动态库优缺点
优点
节省磁盘 & 内存:多个进程共用一份库
热更新:替换新版 libfunc.so,程序重启即可生效,不需要重新编译程序
缺点
运行依赖库文件 :缺少 .so 直接报错(error while loading shared libraries)
运行时有一小段加载开销
4. 最常见报错:找不到 so
编译成功,运行 ./main 报错找不到库
bash
error while loading shared libraries: libfunc.so: cannot open shared object file: No such file or directory
原因:
编译时 -L 只是编译阶段搜索路径,运行时动态链接器不认!
临时解决方案(当前终端生效)
bash
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
./main
永久方案:配置 /etc/ld.so.conf
查看程序依赖哪些库:
bash
ldd ./main
三、静态链接 VS 动态链接 直观对比表
| 特性 | 静态库 .a | 动态库 .so |
|---|---|---|
| 链接时机 | 编译链接期复制代码 | 编译仅登记依赖,运行加载 |
| 程序体积 | 更大 | 更小 |
| 运行依赖库文件 | 不需要 | 必须存在对应 so |
| 多进程内存占用 | 每个进程独立一份代码,浪费 | 内存中只保存一份,共享 |
| 升级库 | 全部程序重新编译 | 替换 so,重启程序即可 |
| 编译参数 | -static 强制静态链接 | 默认优先动态链接 |
四、链接完整底层流程
两种链接模式
1.静态链接
ld链接器:读取.o + 解析.a,抽取需要的目标文件,合并代码段数据段,生成独立 ELF 可执行文件。
2.动态链接(ELF 分为两种)
动态链接可执行文件 :自身不含库代码,依赖 ld-linux 运行时加载 so(日常最常用)
静态 PIE 程序 :全部代码内置,不依赖外部库(较少见)
符号解析(核心概念)
符号:函数名、全局变量名
链接的本质:解决符号引用
main.c 里调用 func() → 符号未定义
链接器去库里面寻找 func 符号的地址,完成绑定。
绑定时机区分
1.静态链接:编译时绑定(早绑定)
2.动态链接默认:运行时绑定(延迟绑定 lazy binding)
第一次调用函数才去寻找地址,可以用 ldd / objdump 观察。
五、必敲实操命令汇总(你可以直接在 Ubuntu 复现)
bash
# 1.生成目标文件
gcc -c -fPIC func.c
# 2.制作静态库
ar rcs libfunc.a func.o
# 3.制作动态库
gcc -shared func.o -o libfunc.so
# 4.静态链接编译
gcc main.c -o main_static -L. -lfunc -static
# 5.动态链接编译
gcc main.c -o main_dyn -L. -lfunc
# 查看依赖
ldd main_dyn
ldd main_static
# 查看库内包含哪些目标文件
ar -t libfunc.a
六、高频面试题总结
为什么动态库必须 -fPIC?
动态库可以加载到虚拟地址任意位置,不能写死地址;位置无关代码保证重定位正常执行。静态库不需要 PIC。
编译能找到库,运行找不到 so 为什么?
-L 只是编译搜索路径;运行由动态链接器 LD 寻找,两套独立路径。
一个程序能不能同时使用静态库和动态库?
可以,一部分代码静态链接,一部分动态链接。
静态库和动态库优先级?
gcc 不加参数,优先动态库;加 -static 强制全部静态链接。
动态库多个进程共享原理?
so 代码段只读,操作系统使用页共享机制;数据段每个进程独立私有。
如果你需要,我可以给你写一套完整可复制的测试代码,你在 Ubuntu 一步步敲,直观看到两种库的区别。
图文化通俗讲解
先记住一条主线:
库 = 一堆写好的函数代码,不给源码,只给打包好的二进制
编译四阶段:源码 → .o目标文件 →【链接】→ 可执行程序
一、静态库 libxxx.a 示意图
制作阶段

静态链接过程【核心图】

二、动态库 libxxx.so 示意图
制作阶段

动态链接过程【重点!不拷贝代码】

链接时,func.o 的代码没有复制进 main!
运行时流程

动态库运行内存示意图(共享精髓)
两个进程运行 ./main

主要作用(从实际角度去理解分类库的作用)
一、那么,我们为什么要分类这库的内容呢,虽然在实际情况中都是默认动态,但我们的古法编程也需要去仔细了解,以后的面经必备!
bash
gcc main.c -o main -L路径 -lxxx
默认情况下:gcc 进行链接时,优先搜寻动态库 .so
如果找不到 .so,才会退而求其次去找静态库 .a
gcc 的搜寻顺序:
查找 libxxx.so(动态库)→ 找到就动态链接
找不到 so,再查找 libxxx.a → 静态链接
但是!纯基础编译 gcc code.c -o code 没有涉及第三方库,不存在动静态选择,链接的是系统标准 C 库!
2、重点:系统 C 库案例(最容易混淆)
bash
gcc code.c -o code
这条命令,默认动态链接 glibc(libc.so.6)
验证:
bash
ldd ./code
# 你能看到依赖 libc.so.6
如果你想要这条命令强制静态链接系统 C 库,必须加 -static
bash
gcc code.c -o code_static -static
ldd ./code_static
# 提示 not a dynamic executable,完全不依赖外部so
3、区分两种场景,彻底分清
场景 A:自己写代码,不引入第三方库
bash
gcc code.c -o code
只链接系统 C 标准库,默认动态链接 glibc ,不用自己管理库文件。
日常写小程序就用这个,不用关心动静态。
场景 B:引入自己制作的库(libxxx.a/libxxx.so)
bash
gcc main.c -o main -L. -lfunc
当前目录同时存在 libfunc.so + libfunc.a
默认优先动态链接(libfunc.so)
只想强制使用静态库,忽略 so:
bash
gcc main.c -o main -L. -lfunc -static
4、实操层面(写代码直接套用)
1.日常练习、小程序
直接 gcc code.c -o code,不用管库,默认动态链接系统库,够用。
2.使用自己制作的库
bash
# 默认优先动态库
gcc main.c -o main -L. -lfunc
# 强制静态链接
gcc main.c -o main -L. -lfunc -static
5、一个极易踩坑的现实问题
就算编译用动态链接成功:
bash
gcc main.c -o main -L. -lfunc
运行 ./main 大概率报错找不到 libfunc.so
原因:
-L. 只是编译阶段库搜索路径,运行时动态加载器不识别!
临时解决方案(当前终端生效)
bash
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
./main
6.检验同时生成两个库是否为优先动态
同时生成 libfunc.a 和 libfunc.so
执行:
bash
gcc main.c -o main -L. -lfunc
ldd ./main
ldd 会打印依赖 libfunc.so,证明默认动态链接。
