动静态库制作与使用

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 来链接它。

  1.   库命名规范:必须以 lib 开头,后缀为 .a(静态)或 .so(动态),否则 -lxxx 无法匹配。
  2. 查看动态库依赖:用 ldd 命令检查可执行文件依赖的动态库:
    ldd main_shared
    # 输出示例(可见libmath.so的路径):
    # libmath.so => ./libmath.so (0x00007fxxxxxx)
    # libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fxxxxxx)
    
  3. 动静态库二选一:若同一目录下同时有 libmath.a 和 libmath.sogcc 优先链接动态库;强制链接静态库需加 -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文件)和相应的头文件放到系统目录中,以便操作系统可以找到它们, 确保其他程序可以链接它们。

步骤:
  1. 获取静态库文件:可能是从源码编译得到,或者是直接下载的预编译库。

  2. 将头文件复制到系统头文件目录,例如/usr/local/include

  3. 将静态库文件复制到系统库目录,例如/usr/local/lib

  4. 更新库缓存(对于动态库是必要的,静态库不需要,但为了保持一致性,可以运行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.cfoo2.cfoo3.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. 设置环境变量(将动态库路径加入系统搜索列表):

    # 方式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)。
  2. 运行程序:

    ./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 标准)

永久将动态库放入系统推荐的用户级目录,全局生效(所有用户 / 程序可识别)。

具体步骤
  1. 拷贝头文件(可选,方便后续编译其他程序时无需指定 - I):
    sudo cp ./mylib/include/mystdio.h /usr/local/include/
    
  2. 拷贝动态库到 /usr/local/lib(系统默认扫描的用户级库目录):
    sudo cp ./mylib/lib/libmystdio.so /usr/local/lib/
    
  3. 刷新动态库缓存(关键,让系统识别新加入的库):
    sudo ldconfig
    
  4. 编译 + 运行程序(此时无需 - 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 [动态库所在的绝对路径] [用户本地目录或则系统核心目录]
具体步骤
  1. 确定动态库的绝对路径(软链接必须用绝对路径,否则易失效):
    # 真实库路径:/home/Howrun/test/mylib/lib/libmystdio.so
    
  2. 创建软链接到 /usr/local/lib(推荐,避免系统风险):
    sudo ln -s /home/Howrun/test/mylib/lib/libmystdio.so /usr/local/lib/libmystdio.so
    
  3. 刷新动态库缓存:
    sudo ldconfig
    
  4. 编译 + 运行程序:
    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/ 配置)

将动态库路径加入系统全局配置,所有用户 / 程序永久识别,适合多用户共享库的场景。

具体步骤
  1. 确定动态库的绝对路径/home/Howrun/test/mylib/lib
  2. 新建系统配置文件(避免修改系统主配置 /etc/ld.so.conf):
    sudo nano /etc/ld.so.conf.d/mylib.conf  # 用nano编辑器新建文件
    
  3. 在编辑器中输入动态库的绝对路径:
    /home/Howrun/test/mylib/lib
    
    • 保存退出:按Ctrl+O保存,按Ctrl+X退出 nano。
  4. 刷新动态库缓存(关键,让配置生效):
    sudo ldconfig
    
  5. 编译 + 运行程序:
    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,推荐)

将库路径硬编码到可执行文件,程序独立运行,无需修改系统 / 环境变量(隔离性最好)。

具体步骤
  1. 编译程序时添加-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字段,运行时优先扫描该路径。
  2. 直接运行程序(无需任何额外配置):

    ./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 rcsgcc -shared + -fPIC
链接时默认优先级低(GCC 优先选.so)高(GCC 默认选.so)
强制链接方式加 -static 或显式指定.a仅保留.so,或不加 -static
3. 兼容性
  • 动态库的 .o 文件必须加 -fPIC(否则链接失败);
  • 静态库的 .o 文件加不加 -fPIC 都可以(加了不影响);
  • 若库依赖其他库(如 libclibm),动静态版本会继承这些依赖(静态库会把依赖的静态库也拷贝进来,动态库仅记录依赖引用)。

七、总结

一个库同时提供动静态版本的核心是:

  1. 共用源码编译出 .o 文件(动态库需 -fPIC);
  2. 用 ar rcs 生成 .a,用 gcc -shared 生成 .so
  3. 保证两个库的命名前缀一致(libxxx),放在同一目录;
  4. 用户可通过 -static 控制链接静态版,默认链接动态版。

这种方式是开源库(如 OpenSSL、MySQL 客户端库)的常规做法,能满足 “常规动态部署” 和 “高移植静态部署” 两种需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值