
写 C/C++ 程序时我们无时无刻不在使用库:
printf依赖**libc.so、字符串操作依赖底层库**。很多同学只会-l链接参数,不懂静态 / 动态库本质,面试常被问:
- 静态库和动态库链接时机区别?
- 编译动态库为什么必须加
-fPIC?ldd找不到.so有几种解决方案?- ELF 的 section 和 segment 有什么区别?
- GOT、PLT 延迟绑定原理?
本文结合完整代码示例、Makefile、ELF 工具实操、底层加载流程,一次性讲透动静态库全链路原理。

一、什么是库
1.1 库的本质
库(Library)是写好的、现有的、成熟的、可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
本质上来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
1.2 两种库区分
| 类型 | Linux 后缀 | Windows 后缀 | 链接时机 | 核心特点 |
|---|---|---|---|---|
| 静态库 | .a |
.lib |
编译链接阶段 | 代码拷贝进可执行,运行无需原库 |
| 动态库 (共享库) | .so |
.dll |
程序运行阶段 | 仅存依赖,多进程共享同一份库 |
**注意:**Linux 下库有严格的命名规范,编译器靠前后缀识别库类型。
二、静态库 (.a) 制作、使用、原理
2.1 静态库核心定义
编译链接时,链接器会把静态库中被调用的目标代码完整拷贝进可执行文件 。程序编译完成后,删除.a文件程序仍能独立运行。
一个可执行程序可能会用到许多库,这些库运行时有的是静态库,有的是动态库。我们的编译默认认为动态链接库 ,只有在该库下找不到动态.so的时候才会采用同名静态库。我们也可以使用gcc的 -static强制设置链接静态库。
2.2静态库的制作与打包
1.创建前提文件

2.静态库制作 Makefile
libmystdio.a:my_stdio.o my_string.o
@ar -rc $@ $^
@echo "构建静态库完成"
%.o:%.c
@gcc -c $<
@echo "编译源文件 $<"
# 清理
.PHONY:clean
clean:
@rm -rf *.a *.o stdc*
# 打包输出头文件+库
.PHONY:output
output:
mkdir -p stdc/include
mkdir -p stdc/lib
cp -f *.h stdc/include
cp -f *.a stdc/lib
tar -czf stdc.tgz stdc
命令说明
ar:归档工具,打包多个.o为.aar -rc:r = 替换已有文件,c = 不存在则创建库
查看库内部目标文件:
ar -tv libmystdio.a


3.静态库的链接使用
首先创建main.c文件,文件中要用到我们自己实现的库

原因: 链接器只会检索:环境变量 LIBRARY_PATH → 系统默认库目录(/usr/lib、/usr/local/lib),但是系统默认库目录下并没有这些函数
1.不同场景
1.头文件和库文件安装到系统默认路径下
在linux中,头文件一般放在**/user/include**目录下;
库文件在**/usr/lib64**目录中
我们把自己当.h文件和.a文件分别打包到对应的目录中
sudo cp -r stdc/include/* /usr/include/
sudo cp stdc/lib/libmystdio.a /usr/lib64/
gcc main.c -lmystdio

为什么我们平时的gcc直接就生成可执行文件了,而用自己的库还要指定库文件呢?
👉 gcc 内置规则:默认自动追加 -lc,链接 C 标准库。
libc存放printf、fopen这类系统函数实现。
自定义静态库 libmystdio.a
gcc没有任何一条规则会自动链接你自己写的库 ! 编译器无法预判你要用哪个自制库,所以必须手动写 -lmystdio。
2.头文件和库文件都在当前路径下
gcc main.c -L. -l库文件名

3:头文件和库文件有自己的独立路径
gcc main.c -I头文件路径 -L库文件路径 -l库文件名
4.参数说明
• -L:指定库文件路径
• -I:指定头文件搜索路径
• -l:指定库名**库文件命名规则:**库名称去掉前缀 lib,去掉后缀 .so / .a。例如 libc.so -> -lc
特点: 静态库链接后,测试目标文件生成,删掉静态库,程序照样可以运行。因为代码已经拷贝到了可执行文件中。
三、动态库(.so)
3.1 什么是动态库
动态库(.so) :程序在运行的时候才去链接动态库的代码 ,多个程序可以共享使用库的代码。
核心特点:
- 一个与动态库链接的可执行文件仅仅包含它要用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中------这个过程称为动态链接(dynamic linking)
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间
- 操作系统采用虚拟内存机制,允许物理内存中的一份动态库被映射到该库的所有进程共用,节省了内存和磁盘空间
3.2动态库的制作与打包
# Makefile
libmystdio.so:my_stdio.o my_string.o
gcc -o $@ $^ -shared
%.o:%.c
gcc -fPIC -c $<
.PHONY:clean
clean:
@rm -rf *.so *.o stdc*
@echo "clean ... done"
.PHONY:output
output:
@mkdir -p stdc/include
@mkdir -p stdc/lib
@cp -f *.h stdc/include
@cp -f *.so stdc/lib
@tar -czf stdc.tgz stdc
@echo "output stdc ... done"
关键参数说明:
-shared:表示生成共享库格式
-fPIC:产生位置无关码(Position Independent Code)
库名规则:libxxx.so


3.3 动态库的链接使用
1.三种场景(与静态库一致)
1.头文件和库文件安装到系统路径下
$ gcc main.c -l库名
2.头文件和库文件都在当前路径下
gcc main.c -L. -l库名
3.头文件和库文件有自己的独立路径
gcc main.c -I头文件路径 -L库文件路径 -l库名
3.4 动态库运行搜索路径问题
编译可以通过,但运行时找不到动态库:

查看文件依赖库

1.四种解决方案
方案1:拷贝.so文件到系统共享库路径
一般指 /usr/lib、/usr/local/lib、/lib64
sudo cp libmystdio.so /lib64
sudo cp -r stdc/include/ /usr/include
接下来重新运行文件即可

方案2:在系统共享库路径下建立同名软连接
sudo ln /home/wyy/my-project/linux-practice/动态库/libmystdio.so /lib64/libmystdio.so

方案3:更改环境变量 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wyy/my-project/linux-practice/动态库/mystdio

注意:exprot仅当前打开的终端窗口有效,关闭终端就失效
方案4:ldconfig 配置方案配置
# 1.新建配置文件(需要sudo)
sudo vim /etc/ld.so.conf.d/mylib.conf
# 文件内写入动态库所在目录:
/home/wyy/my-project/linux-practice/动态库/mystdio
# 2.更新系统动态库缓存
sudo ldconfig
生效范围:全局永久生效,所有用户、所有终端都可用。
四、总结
本文从"什么是库"出发,详细介绍了:
- 静态库(.a) :编译时链接到程序中,运行时不再需要。制作使用
ar归档工具,使用-L、-l、-I参数链接。 - 动态库(.so) :运行时才链接,可被多个程序共享。制作使用
-shared和-fPIC选项。运行时可能遇到搜索路径问题,可通过拷贝到系统路径、设置LD_LIBRARY_PATH或ldconfig解决。
理解这些概念,对于解决实际的链接错误、优化程序体积和管理库依赖都至关重要。