Linux 动静态库的核心概念
库是编译好的可重用代码集合,Linux 下分为静态库和动态库,核心区别在于「代码融入可执行文件的时机」:
| 对比维度 | 静态库(Linux:.a) | 动态库(Linux:.so) |
|---|---|---|
| 核心原理 | 编译链接阶段,链接器将库中被调用的代码完整拷贝到可执行文件中 | 编译链接阶段,仅在可执行文件中记录依赖引用(不拷贝代码);运行时由操作系统加载库代码 |
| 文件后缀(Linux) | .a(Archive,归档文件) | .so(Shared Object,共享对象) |
| 编译阶段依赖 | 需 -I 指定头文件路径(检查函数声明)、-L 指定库路径(找到.a)、-lxxx 链接库 | 同静态库:需 -I 头文件、-L 库路径、-lxxx 链接库(流程完全一致) |
| 运行阶段依赖 | 无任何依赖:可执行文件已包含库代码,无需头文件 / 静态库文件,直接运行 | 需操作系统找到.so 文件(头文件无用):① 临时:LD_LIBRARY_PATH;② 永久:ld.so.conf+ldconfig;③ 推荐:编译时 -Wl,-rpath=路径 |
| 可执行文件大小 | 较大:包含库代码(若多程序使用,会重复拷贝,冗余) | 较小:仅记录引用,多程序共享同一份库代码 |
| 库更新成本 | 高:库代码已拷贝到可执行文件,库更新后需重新编译所有依赖该库的程序 | 低:仅替换.so 文件(接口不变时),所有依赖程序无需重新编译 |
| 内存 / 磁盘占用 | 高:多程序运行时,各程序加载一份库代码到内存;磁盘存多份拷贝 | 低:多程序共享一份库代码(内存中仅加载一次);磁盘仅存一份.so |
| 编译参数差异 | 生成:gcc -c 编译.o → ar rcs libxxx.a xxx.o;使用:gcc -L. -lxxx(可加-static强制静态链接) | 生成:gcc -c -fPIC 编译位置无关.o → gcc -shared -o libxxx.so xxx.o;使用:gcc -L. -lxxx(需解决运行路径) |
| 移植性 | 强:可执行文件独立,拷贝到同架构 Linux 系统可直接运行(无库依赖) | 弱:需同时拷贝.so 文件,或确保目标系统有对应.so(版本需兼容) |
| 调试 / 兼容性风险 | 低:无运行时库版本冲突(代码已固化) | 高:若系统中.so 版本与编译时不一致,可能触发 “版本找不到” 等运行时错误 |
| 系统级风险(安装) | 低:即使安装到系统目录,.a 仅编译时用,不会覆盖运行时核心库 | 高:若自定义.so 覆盖系统核心.so(如 libc.so.6),会导致系统崩溃 |
库文件通常以 lib 开头,以 .a(静态库)或 .so(动态库)结尾。
使用 -l 选项时,链接器会自动在指定的目录中搜索名为 libfoo.a 或 libfoo.so 的文件。
所以,如果你有一个库文件叫做 libfoo.a 或 libfoo.so,那么你可以用 -lfoo 来链接它。
- 库命名规范:必须以
lib开头,后缀为.a(静态)或.so(动态),否则-lxxx无法匹配。- 查看动态库依赖:用
ldd命令检查可执行文件依赖的动态库:ldd main_shared # 输出示例(可见libmath.so的路径): # libmath.so => ./libmath.so (0x00007fxxxxxx) # libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fxxxxxx)- 动静态库二选一:若同一目录下同时有
libmath.a和libmath.so,gcc优先链接动态库;强制链接静态库需加-static:gcc main.c -o main_static_force -L. -lmath -static
章节1. 静态库(.a)的制作与使用
1. 制作静态库
步骤:编译生成目标文件(.o)→ 打包为静态库(.a)。
# 1. 编译math.c为目标文件(-c:只编译不链接,-o指定输出) gcc -c math.c -o math.o # 2. 用ar工具打包为静态库(命名规范:libxxx.a) # ar参数:r(替换已有文件)、c(创建库)、s(建立索引,加速链接) ar rcs libmath.a math.o执行后生成
libmath.a,即静态库文件。
2. 多个文件创建静态库
同样,对于多个源文件创建静态库,我们使用
ar命令。# 1. 编译目标文件(静态库不需要-fPIC,但也可以加上,以便将来用于动态库) gcc -c foo1.c -o foo1.o gcc -c foo2.c -o foo2.o gcc -c foo3.c -o foo3.o # 2. 使用ar命令打包成静态库 ar rcs libfoo.a foo1.o foo2.o foo3.o # 注意:如果有多个目标文件,可以一次性列出,或者使用通配符 ar rcs libfoo.a *.o
3. 用Makefile构建自定义静态库并整理库文件结构
项目目录:
项目目录/ ├── Makefile ├── mystdio.c # 源文件1 ├── mystdio.h # 头文件1 ├── mystring.c # 源文件2 ├── mystring.h # 头文件2 └── main.c # 主程序(不在Makefile中)makefile代码:
# ========== 1. 静态库构建规则 ========== # 目标:生成静态库libmystdio.a # 依赖:需要先编译出mystdio.o和mystring.o这两个目标文件 libmystdio.a:mystdio.o mystring.o # ar是静态库打包工具 # -r:若库中已有同名文件则替换;-c:若库不存在则创建 # $@:自动变量,代表当前规则的“目标”(即libmystdio.a) # $^:自动变量,代表当前规则的“所有依赖”(即mystdio.o mystring.o) # 作用:将 mystdio.o 和 mystring.o 打包成静态库 libmystdio.a ar -rc $@ $^ # ========== 2. 通用编译规则(.c → .o) ========== # 模式规则:匹配所有“目标名.o”的文件,依赖对应的“目标名.c”文件 # (比如要生成mystdio.o,会自动找mystdio.c作为依赖) %.o:%.c # gcc -c:只编译不链接,将.c源文件编译为.o目标文件 # $<:自动变量,代表当前规则的“第一个依赖”(即对应的.c文件) # 作用:将 .c 源文件编译为对应的 .o 目标文件 gcc -c $< # ========== 3. 清理编译产物规则 ========== # .PHONY:声明“clean”是伪目标(防止当前目录有同名文件时,make误判为文件而不执行) .PHONY:clean # 目标:clean(执行make clean时触发) clean: # rm -f:强制删除文件(忽略不存在的文件) # 删除所有.o目标文件、.a静态库、以及名为output的文件 rm -f *.o *.a output # ========== 4. 整理库文件结构规则 ========== # .PHONY:声明“output”是伪目标 .PHONY:output # 目标:output(执行make output时触发,用于整理库的目录结构) output: # mkdir -p:递归创建目录(若目录已存在则不报错) # 创建根目录mylib mkdir -p mylib # 在mylib下创建头文件目录include mkdir -p mylib/include # 在mylib下创建库文件目录lib mkdir -p mylib/lib # 把当前目录下所有.h头文件,复制到mylib/include(供其他程序引用) cp *.h mylib/include # 把当前目录下所有.a静态库,复制到mylib/lib(供其他程序链接) cp *.a mylib/lib执行 make output 后的目录结构
项目目录/ ├── mylib/ # 新创建的发布目录 │ ├── include/ # 包含所有头文件 │ │ ├── mystdio.h │ │ └── mystring.h │ └── lib/ # 包含静态库 │ └── libmystdio.a └── ... # 其他源文件
4. 使用外部静态库
你在编译程序的过程中, 想要使用外部库, 那么必须
1. 让操作系统找到你需要调用的库(编译时显式指明或者加入本地目录/系统目录)
2. 指出你要用的是哪个库(gcc main.c -lmath -o main, 指明要用libmath.a)
运行时的依赖:无
方法1
安装外部的静态库通常意味着将静态库文件(.a文件)和相应的头文件放到系统目录中,以便操作系统可以找到它们, 确保其他程序可以链接它们。
步骤:
获取静态库文件:可能是从源码编译得到,或者是直接下载的预编译库。
将头文件复制到系统头文件目录,例如
/usr/local/include。将静态库文件复制到系统库目录,例如
/usr/local/lib。更新库缓存(对于动态库是必要的,静态库不需要,但为了保持一致性,可以运行
ldconfig,不过ldconfig只处理动态库)。示例:
假设我们已经有一个静态库libmystdio.a和两个头文件mystdio.h和mystring.h存放在mylib/中,我们希望将它们安装到系统中。
项目目录/ ├── mylib/ # 新创建的发布目录 │ ├── include/ # 包含所有头文件 │ │ ├── mystdio.h │ │ └── mystring.h │ └── lib/ # 包含静态库 │ └── libmystdio.a └── ... # 其他源文件步骤1:
写法1:安装到用户本地目录
# 复制头文件 sudo cp mylib/include/* /usr/local/include/ # 复制静态库 sudo cp mylib/lib/libmystdio.a /usr/local/lib/ # 注意:静态库不需要运行ldconfig,因为ldconfig只用于更新动态库的缓存。 # 但是,如果同时安装了动态库,那么运行一下也无妨。 sudo ldconfig写法2:安装到系统核心目录
# 复制头文件 sudo cp mylib/include/* /usr/include/ # 复制静态库 sudo cp mylib/lib/libmystdio.a /lib64/ # 注意:静态库不需要运行ldconfig,因为ldconfig只用于更新动态库的缓存。 # 但是,如果同时安装了动态库,那么运行一下也无妨。 sudo ldconfig
对比项目 方法1:安装到用户本地目录 方法2:安装到系统目录 头文件目标 /usr/local/include//usr/include/库文件目标 /usr/local/lib//lib64/适用场景 用户级本地安装 系统级全局安装 权限需求 需要root权限 需要root权限 标准性 标准做法 非标准做法 推荐程度 强烈推荐 不推荐 这两个方法无论是静态库还是动态库都有效
步骤2:
使用gcc直接编译,由于库文件和头文件都在系统默认路径下,我们可以直接使用-l选项链接库, 可以省略 -L
# 编译主程序并链接静态库 # 方法一:用-lmath表示libmath.a gcc main.c -lmath -o main # 方法二:用路径 gcc main.c /usr/local/lib/libmath.a -o main # 运行,因为静态库已经编译进程序,所以不需要额外的库文件 ./program
方法2:
如果库不在默认路径,我们可以通过-I指定头文件路径,通过-L指定库文件路径
gcc main.c -o main -I ./mylib/include/ -L ./mylib/lib/ -lmystdio # 运行,因为静态库已经编译进程序,所以不需要额外的库文件 ./main
参数 作用对象 生效阶段 核心作用 -I 路径头文件(.h) 编译阶段(预处理 / 编译) 告诉 GCC:去哪里找 #include引用的头文件(比如#include "foo.h")-L 路径库文件(.so/.a) 编译阶段(链接) 告诉 GCC:去哪里找要链接的库文件(比如 -lfoo对应的libfoo.so/libfoo.a)LD_LIBRARY_PATH动态库(.so) 运行阶段 告诉操作系统:去哪里加载可执行文件依赖的动态库(编译完成后生效) -I这个命令的核心目的是:编译
main.c时,让 GCC 除了默认的头文件搜索路径(如系统头文件目录/usr/include、当前目录)外,额外去./mylib/include/目录找需要包含的头文件,同时只生成main.o
但是,由于/usr/local/include和/usr/local/lib是gcc默认搜索路径,所以通常可以省略。
然而,有些系统可能没有将/usr/local/include和/usr/local/lib作为默认搜索路径,此时我们可以通过设置环境变量来添加,或者每次编译时都使用-I和-L。
补充:
如果我们想确保链接的是静态库而不是动态库,可以使用-static选项:
gcc main.c -static -lmath -o main但是,-static会将所有库都静态链接,包括C标准库,这会导致可执行文件非常大。如果我们只想静态链接特定的库,而其他库仍然动态链接,可以使用-Wl,-Bstatic和-Wl,-Bdynamic选项。例如,我们只想静态链接math库,而其他库使用动态链接:
gcc main.c -Wl,-Bstatic -lmath -Wl,-Bdynamic -o main编译测试程序时,指定「库路径」和「库名」:
# -L. :指定库所在路径(. 表示当前目录) # -lmath:链接libmath.a(自动省略lib和.a,仅保留math) # 方法一:用"-L. -lmath"表示libmath.a的路径 gcc main.c -L. -lmath -o main_static # 方法二:用路径 gcc main.c /path/to/your/libmath.a -o main_static # 运行程序(无需依赖外部库,直接执行) ./main_static # 输出: # 3+5=8 # 3*5=15
"-L. -lfmath"中:
-L方法 : -L后面的内容表示你的静态库所在的目录的路径,这里是" . ",表示当前所在的路径cwd
-lfmath表示" . "路径下的文件libmath.a;
-l方法:
如果你的文件名是libfoo.a那么就是-lfoo,如果是libabc.a就是-labc
如果你的静态库文件名没有以lib开头就不能用-l方法.
下载C语言静态库
yum install glibc-static libstdc++-static -y
使用C语言静态库编译代码
gcc main.c -o main_static -L. -static
章节2. 动态库(.so)的制作与使用
动态库在编译时要被编译器找到,在运行时要被操作系统找到
1. 制作动态库
假设有一个源文件:math.c,我们要创建一个动态库 libmath.so
步骤:编译生成「位置无关目标文件」→ 链接为动态库(.so)。
# 1. 编译为位置无关目标文件(-fPIC:生成位置无关代码,动态库必需) gcc -c -fPIC math.c -o math.o # 2. 链接为动态库(-shared:指定生成共享库,命名规范:libxxx.so) gcc -shared -o libmath.so math.o假设有三个源文件:
foo1.c,foo2.c,foo3.c,我们要创建一个动态库libfoo.so。步骤:
# 1. 分别将每个源文件编译成位置无关的目标文件 gcc -c -fPIC foo1.c -o foo1.o gcc -c -fPIC foo2.c -o foo2.o gcc -c -fPIC foo3.c -o foo3.o # 2. 将多个目标文件链接成一个共享库 gcc -shared -o libfoo.so foo1.o foo2.o foo3.o # 用通配符省略 gcc -shared -o libfoo.so *.o # 或者一步完成:将所有源文件直接编译并链接成共享库 gcc -shared -fPIC -o libfoo.so foo1.c foo2.c foo3.c # 用通配符省略 gcc -shared -fPIC -o libfoo.so *.c执行后生成
libmath.so,即动态库文件。
2. 使用动态库
动态库使用和静态库有很大差异, 因为静态库只需在编译的时候指出库的位置以及头文件位置,然后编译器导入这两个东西后,编译结束后, 可以直接使用了;但是动态库在编译的时候虽然指出了库文件和头文件的位置,完成编译后,执行的时候,还需要让操作系统能过找到你的动态库(头文件不需要)
(1)编译测试程序
# 编译命令和静态库类似(-lmath自动匹配libmath.so) gcc main.c -o main_shared -L. -lmath(2)运行动态库程序(关键:解决库加载路径)
直接运行会报错, 系统找不到你指明的库的位置(系统默认只搜索
/lib、/usr/lib等路径):./main_shared # 报错:error while loading shared libraries: libmath.so: cannot open shared object file: No such file or directory
运行时如何保证操作系统可以找到库
方法 1:临时设置 LD_LIBRARY_PATH(终端会话内有效)
临时让当前终端识别动态库路径,仅用于单次测试 / 调试。
具体步骤
-
设置环境变量(将动态库路径加入系统搜索列表):
# 方式1:相对路径(推荐,与工作目录绑定) export LD_LIBRARY_PATH=./mylib/lib:$LD_LIBRARY_PATH # 方式2:绝对路径(更稳定,不受当前目录影响) export LD_LIBRARY_PATH=/home/Howrun/test/mylib/lib:$LD_LIBRARY_PATH- 解释:
$LD_LIBRARY_PATH保留原有环境变量,避免覆盖系统默认路径; - 多路径用冒号分隔(如
LD_LIBRARY_PATH=/path1:/path2:$LD_LIBRARY_PATH)。
- 解释:
-
运行程序:
./main_shared
验证生效
- 程序正常输出,无 “cannot open shared object file” 错误;
- 用
ldd main_shared查看,显示libmystdio.so => ./mylib/lib/libmystdio.so。
注意事项
- 关闭终端后环境变量失效,新终端需重新执行
export命令; - 优先级高于系统默认路径,易引发版本冲突(如加载到旧版本库)。
方法 2:安装到用户本地目录(/usr/local/lib)或系统核心目录(/lib64,不推荐)
子场景 2.1:安装到 /usr/local/lib(推荐,符合 Linux 标准)
永久将动态库放入系统推荐的用户级目录,全局生效(所有用户 / 程序可识别)。
具体步骤
- 拷贝头文件(可选,方便后续编译其他程序时无需指定 - I):
sudo cp ./mylib/include/mystdio.h /usr/local/include/ - 拷贝动态库到 /usr/local/lib(系统默认扫描的用户级库目录):
sudo cp ./mylib/lib/libmystdio.so /usr/local/lib/ - 刷新动态库缓存(关键,让系统识别新加入的库):
sudo ldconfig - 编译 + 运行程序(此时无需 - L 指定库路径,因为 /usr/local/lib 是默认路径):
# 重新编译(简化命令,头文件/库都在默认路径) gcc main.c -lmystdio -o main_shared # 运行程序 ./main_shared
验证生效
ldd main_shared显示libmystdio.so => /usr/local/lib/libmystdio.so;- 关闭终端 / 重启系统后仍能正常运行。
注意事项
- /usr/local/lib 是 Linux FHS 标准目录,无系统风险;
- 卸载库:
sudo rm /usr/local/lib/libmystdio.so && sudo ldconfig。
子场景 2.2:安装到 /lib64(绝对不推荐,仅说明步骤)
具体步骤
# 拷贝库到系统核心库目录(极度危险!)
sudo cp ./mylib/lib/libmystdio.so /lib64/
sudo ldconfig
# 编译运行
gcc main.c -I ./mylib/include -lmystdio -o main_shared && ./main_shared
注意事项
- /lib64 存放系统核心库(如 libc.so.6),自定义库易覆盖系统库,导致
ls/cd等基础命令崩溃; - 生产环境严禁使用此方法!
方法 3:在系统 / 用户目录创建软链接(不移动真实库文件)
不拷贝库文件,仅创建软链接指向真实路径,永久生效,适合 “库路径固定、需切换版本” 的场景。
sudo ln -s /home/Howrun/mylib/lib/libmystdio.so /lib64/libmystdio.so
sudo ln -s [动态库所在的绝对路径] [用户本地目录或则系统核心目录]
具体步骤
- 确定动态库的绝对路径(软链接必须用绝对路径,否则易失效):
# 真实库路径:/home/Howrun/test/mylib/lib/libmystdio.so - 创建软链接到 /usr/local/lib(推荐,避免系统风险):
sudo ln -s /home/Howrun/test/mylib/lib/libmystdio.so /usr/local/lib/libmystdio.so - 刷新动态库缓存:
sudo ldconfig - 编译 + 运行程序:
gcc main.c -I ./mylib/include -lmystdio -o main_shared ./main_shared
验证生效
ldd main_shared显示libmystdio.so => /usr/local/lib/libmystdio.so (指向 /home/Howrun/test/mylib/lib/libmystdio.so);- 运行程序无报错。
注意事项
- 切换库版本:只需删除旧软链接,创建新的即可(如
sudo ln -s /new/path/libmystdio.so /usr/local/lib/); - 删除软链接:
sudo rm /usr/local/lib/libmystdio.so(不会删除真实库文件)。
方法 4:永久设置(修改 /etc/ld.so.conf.d/ 配置)
将动态库路径加入系统全局配置,所有用户 / 程序永久识别,适合多用户共享库的场景。
具体步骤
- 确定动态库的绝对路径:
/home/Howrun/test/mylib/lib。 - 新建系统配置文件(避免修改系统主配置 /etc/ld.so.conf):
sudo nano /etc/ld.so.conf.d/mylib.conf # 用nano编辑器新建文件 - 在编辑器中输入动态库的绝对路径:
/home/Howrun/test/mylib/lib- 保存退出:按
Ctrl+O保存,按Ctrl+X退出 nano。
- 保存退出:按
- 刷新动态库缓存(关键,让配置生效):
sudo ldconfig - 编译 + 运行程序:
gcc main.c -I ./mylib/include -lmystdio -o main_shared ./main_shared
验证生效
ldd main_shared显示libmystdio.so => /home/Howrun/test/mylib/lib/libmystdio.so;- 重启系统后仍能正常运行。
注意事项
- 配置文件中必须写绝对路径(不能写
./); - 若要删除路径:编辑
/etc/ld.so.conf.d/mylib.conf删除对应行,再执行sudo ldconfig。
方法 5:编译时指定运行时库路径(-Wl,-rpath,推荐)
将库路径硬编码到可执行文件,程序独立运行,无需修改系统 / 环境变量(隔离性最好)。
具体步骤
-
编译程序时添加
-Wl,-rpath参数(指定运行时的库路径):# 方式1:绝对路径(推荐,稳定,不受运行目录影响) gcc main.c -I ./mylib/include -L ./mylib/lib -lmystdio -o main_shared -Wl,-rpath=/home/Howrun/test/mylib/lib # 方式2:相对路径(需保证程序与库的相对位置不变) gcc main.c -I ./mylib/include -L ./mylib/lib -lmystdio -o main_shared -Wl,-rpath=./mylib/lib- 解释:
-Wl,-rpath=路径告诉链接器(ld),将路径写入可执行文件的RUNPATH字段,运行时优先扫描该路径。
- 解释:
-
直接运行程序(无需任何额外配置):
./main_shared
验证生效
- 用
readelf -d main_shared | grep RUNPATH查看,能看到硬编码的路径(如RUNPATH Library runpath: [/home/Howrun/test/mylib/lib]); ldd main_shared显示libmystdio.so指向硬编码的路径;- 关闭终端 / 拷贝程序到其他目录(绝对路径版),仍能正常运行。
注意事项
- 相对路径版:移动程序时需同步移动
mylib/lib目录(保证相对位置不变); - 硬编码路径优先级最高,不会被
LD_LIBRARY_PATH覆盖,能精准控制库版本。
总结:各方法优先级与适用场景
| 方法 | 优先级 | 适用场景 |
|---|---|---|
| 方法 5 | ✅ 首选 | 独立程序、生产环境(隔离性好,无系统污染) |
| 方法 2(/usr/local/lib) | ✅ 次选 | 多用户 / 多程序共享的自定义库(符合 Linux 标准) |
| 方法 4 | ✅ 备选 | 服务器批量部署(多程序依赖同一路径库) |
| 方法 3 | ✅ 特殊场景 | 库路径固定、需频繁切换版本(无需拷贝文件) |
| 方法 1 | ✅ 仅测试 | 单次调试 / 临时验证(持久性差) |
| 方法 2(/lib64) | ❌ 禁用 | 任何场景都不推荐(系统崩溃风险) |
3. makefile制作动态库
项目目录/ ├── Makefile ├── mystdio.c # 源文件1 ├── mystdio.h # 头文件1 ├── mystring.c # 源文件2 ├── mystring.h # 头文件2 └── main.c # 主程序(不在Makefile中)
# 执行后: 项目目录/ ├── mylib/ # 新创建的发布目录 │ ├── include/ # 包含所有头文件 │ │ ├── mystdio.h │ │ └── mystring.h │ └── lib/ # 包含动态库 │ └── libmystdio.so └── ... # 其他源文件
# ========== 1. 动态库构建规则 ==========
# 目标:生成动态库libmystdio.so(动态库命名需以lib开头、.so结尾)
# 依赖:需先编译出mystdio.o和mystring.o这两个位置无关的目标文件
libmystdio.so:mystdio.o mystring.o
# gcc -shared:指定生成动态共享库
# -o $@:将输出文件命名为当前规则的目标(即libmystdio.so)
# $^:表示当前规则的所有依赖文件(即mystdio.o mystring.o)
gcc -shared -o $@ $^
# ========== 2. 通用编译规则(生成动态库所需的目标文件) ==========
# 模式规则:匹配所有“xxx.o”文件,依赖对应的“xxx.c”源文件
# (例如要生成mystdio.o,自动依赖mystdio.c)
%.o:%.c
# gcc -c:只编译不链接,生成目标文件
# -fPIC:生成“位置无关代码(Position-Independent Code)”,是动态库的必备参数
# (保证动态库加载到任意内存地址都能正常运行)
# $<:表示当前规则的第一个依赖文件(即对应的xxx.c)
gcc -fPIC -c $<
# ========== 3. 清理编译产物规则 ==========
# .PHONY:声明clean是“伪目标”,避免当前目录存在同名文件时make误判
.PHONY:clean
# 目标:clean(执行make clean时触发)
clean:
# rm -rf:强制递归删除文件/目录(忽略不存在的文件)
# 删除所有.o目标文件、.so动态库、以及mylib输出目录
rm -rf *.o *.so mylib
# ========== 4. 整理动态库目录结构规则 ==========
# .PHONY:声明output是伪目标
.PHONY:output
# 目标:output(执行make output时触发,用于规范动态库的发布结构)
output:
# mkdir -p:递归创建目录(目录已存在则不报错)
mkdir -p mylib # 创建动态库的根目录
mkdir -p mylib/include # 创建头文件存放目录(供其他程序引用接口)
mkdir -p mylib/lib # 创建动态库文件存放目录
cp *.h mylib/include # 将当前目录下的所有头文件(.h)复制到include目录
cp *.so mylib/lib # 将当前目录下的所有动态库(.so)复制到lib目录
编译动态库的程序

# 使用发布的动态库编译程序
# 假设我们已经执行了 make output
# 方法1:使用 -L 和 -l 选项
gcc main.c -I./mylib/include -L./mylib/lib -lmystdio -o myprogram
# 方法2:直接指定库文件路径
gcc main.c -I./mylib/include ./mylib/lib/libmystdio.so -o myprogram
章节3. 混合使用多个库
在实际项目中,可能会同时使用多个静态库和动态库。例如,假设我们有两个静态库 liba.a 和 libb.a,以及一个动态库 libc.so,可以这样链接:
gcc main.c -L. -la -lb -lc -o program
注意:链接器会按照从左到右的顺序解析符号,所以库的顺序很重要。如果库之间存在依赖,被依赖的库应该放在后面。
无论有多少个源文件,创建库的原理是一样的。对于动态库,将所有目标文件用 -shared 和 -fPIC 选项链接起来;对于静态库,用 ar 命令打包。在链接主程序时,使用 -l 指定库名,用 -L 指定库的路径。
关键补充
-static 选项核心解析
-static 是 GCC 编译时的核心选项,作用是强制编译器执行「全静态链接」 —— 链接阶段优先选择静态库(.a)而非动态库(.so),最终生成的可执行文件会包含所有依赖的库代码(包括系统 C 库、自定义库),完全不依赖任何动态库,可独立运行。
# 基础用法(链接自定义静态库libmath.a + 系统静态库glibc-static)
gcc main.c -o main_static -L. -lmath -static
# -static-libgcc 仅影响「GCC 运行时库」的链接方式,完全不改变 -lmath 的链接规则。
# -lmath 对应的库(libmath.so/libmath.a)默认优先链接动态版本(.so)
gcc main.c -o main_mix -L. -lmath -static-libgcc
# 显式链接静态库,系统库仍动态(不使用-lxxx)
gcc main.c -o main_mix ./libmath.a
注意: 如果我动态库里面有一个函数A,静态库里面有一个函数B,那么我编译的时候加上-static,函数A不能调用成功,编译时会直接报「函数 A 未定义引用」的错误 —— 因为 -static 会强制链接器完全忽略所有动态库,只搜索静态库(.a)中的函数实现,而函数 A 仅存在于动态库中,链接器找不到 A 的定义,最终编译失败。
为了避免这种情况,我们可以让一个库同时提供静态版本(.a) 和动态版本(.so), 最终将两个版本放在同一目录(或规范目录)即可
章节4. 一个库如何同时提供两个动静态版本
要让一个库同时提供静态版本(.a) 和动态版本(.so),核心思路是:基于同一套源码,通过不同的编译 / 打包参数,分别生成两种格式的库文件(二者共用源码,仅编译 / 链接参数不同),最终将两个版本放在同一目录(或规范目录)即可。
以下是完整的实操步骤,以自定义 libmath 库为例(包含 add/mul 函数),从源码到同时提供动静态版本的全流程:
一、准备基础源码(共用一套)
创建 2 个文件,作为库的核心源码(动静态版本共用):
1. 头文件 math.h(函数声明)
#ifndef MATH_H
#define MATH_H
// 声明库函数
int add(int a, int b);
int mul(int a, int b);
#endif
2. 实现文件 math.c(函数定义)
#include "math.h"
// 实现加法
int add(int a, int b) {
return a + b;
}
// 实现乘法
int mul(int a, int b) {
return a * b;
}
二、编译生成「共用的目标文件(.o)」
动态库需要「位置无关代码(-fPIC)」,静态库无需,但为了共用 .o 文件(减少编译冗余),统一加 -fPIC(对静态库无负面影响):
# 编译生成位置无关的目标文件 math.o(动静态库共用)
gcc -c -fPIC math.c -o math.o
-c:只编译不链接,生成.o目标文件;-fPIC:生成位置无关代码(动态库必备,静态库兼容)。
三、分别生成静态库(.a)和动态库(.so)
1. 生成静态库 libmath.a
用 ar 工具将 .o 文件打包为静态库(归档文件):
# ar rcs:创建/更新静态库,参数含义:
# r:替换库中已有文件(若存在);c:创建库(若不存在);s:生成索引(加速链接)
ar rcs libmath.a math.o
2. 生成动态库 libmath.so
用 gcc -shared 将 .o 文件链接为动态库:
# -shared:指定生成动态共享库;-o:输出动态库文件
gcc -shared -o libmath.so math.o
四、规范目录结构(可选,推荐)
将头文件和两个版本的库整理到标准目录,方便用户使用:
# 创建目录结构
mkdir -p mylib/include # 存放头文件
mkdir -p mylib/lib # 存放动静态库
# 拷贝文件
cp math.h mylib/include/
cp libmath.a libmath.so mylib/lib/
最终目录结构:
mylib/
├── include/
│ └── math.h # 头文件
└── lib/
├── libmath.a # 静态库
└── libmath.so # 动态库
五、验证:两个版本均可被链接使用
此时 mylib/lib 下同时有 libmath.a 和 libmath.so,可分别测试静态 / 动态链接:
1. 测试动态链接(默认优先链接.so)
# 编译:-I指定头文件路径,-L指定库路径,-lmath链接libmath(默认选.so)
gcc main.c -o main_dynamic -I mylib/include -L mylib/lib -lmath
# 运行(需指定动态库路径,或用之前的方法解决)
export LD_LIBRARY_PATH=mylib/lib:$LD_LIBRARY_PATH
./main_dynamic
2. 测试静态链接(加 -static 强制选.a)
# 编译:加-static强制链接静态库(需系统安装静态C库)
gcc main.c -o main_static -I mylib/include -L mylib/lib -lmath -static
# 运行(无需任何动态库依赖)
./main_static
六、关键注意事项
1. 命名规范(必须遵守)
动静态库需共用「前缀(libxxx)」,仅后缀不同:
- 静态库:
libxxx.a(如libmath.a); - 动态库:
libxxx.so(如libmath.so);这样 GCC 才能通过-lxxx(如-lmath)自动识别两个版本。
2. 编译参数差异
| 操作 | 静态库(.a) | 动态库(.so) |
|---|---|---|
| 生成核心参数 | ar rcs | gcc -shared + -fPIC |
| 链接时默认优先级 | 低(GCC 优先选.so) | 高(GCC 默认选.so) |
| 强制链接方式 | 加 -static 或显式指定.a | 仅保留.so,或不加 -static |
3. 兼容性
- 动态库的
.o文件必须加-fPIC(否则链接失败); - 静态库的
.o文件加不加-fPIC都可以(加了不影响); - 若库依赖其他库(如
libc、libm),动静态版本会继承这些依赖(静态库会把依赖的静态库也拷贝进来,动态库仅记录依赖引用)。
七、总结
一个库同时提供动静态版本的核心是:
- 共用源码编译出
.o文件(动态库需-fPIC); - 用
ar rcs生成.a,用gcc -shared生成.so; - 保证两个库的命名前缀一致(
libxxx),放在同一目录; - 用户可通过
-static控制链接静态版,默认链接动态版。
这种方式是开源库(如 OpenSSL、MySQL 客户端库)的常规做法,能满足 “常规动态部署” 和 “高移植静态部署” 两种需求。

3131

被折叠的 条评论
为什么被折叠?



