Linux之生成和使用静态库与动态库

本文介绍了Linux环境下如何生成和使用静态库和动态库。


动态库 vs 静态库

特性 动态库(.so) 静态库(.a)
链接方式 运行时动态加载 编译时直接嵌入可执行文件
文件大小 可执行文件较小 可执行文件较大
更新方式 替换 .so 文件即可生效 需重新编译程序
加载速度 稍慢(需运行时加载) 更快(代码已在二进制中)
适用场景 多进程共享、热更新 独立部署、无依赖

静态库

在 Linux/Unix 系统中,静态库(Static Library)是一种包含多个目标文件(.o 文件)的归档文件,通常以 .a 结尾(Archive)。静态库在编译时会被直接链接到可执行文件中,使得程序运行时不再依赖外部库文件。以下是生成静态库的详细步骤:


准备源文件

假设有两个源文件:

  • file1.c
  • file2.c

示例代码

1
2
3
4
5
6
// file1.c
#include <stdio.h>

void func1() {
printf("This is func1()\n");
}
1
2
3
4
5
6
// file2.c
#include <stdio.h>

void func2() {
printf("This is func2()\n");
}

编译生成目标文件(.o 文件)

使用 gccclang 编译源文件,生成位置无关的目标文件:

1
2
gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o
  • -c:只编译不链接,生成 .o 文件。
  • -o:指定输出文件名。

🔍 注意:静态库不需要 -fPIC(位置无关代码),因为代码会被直接嵌入可执行文件。


使用 ar 打包成静态库

静态库的命名规范为 lib<name>.a(例如 libmylib.a)。
使用 ar(归档工具)的 rcs 选项:

1
ar rcs libmylib.a file1.o file2.o
  • r:替换或添加文件到库中。
  • c:静默创建库(不显示警告)。
  • s:生成索引(加快链接速度)。

验证静态库

查看库中包含的 .o 文件

1
ar -t libmylib.a

输出:

1
2
file1.o
file2.o

查看导出的符号(函数/变量)

1
nm libmylib.a

输出示例:

1
2
3
4
5
file1.o:
0000000000000000 T func1

file2.o:
0000000000000000 T func2
  • T 表示该符号在库中定义。

使用静态库

编译并链接静态库

1
gcc main.c -L. -lmylib -o myprogram
  • -L.:指定库的搜索路径(. 表示当前目录)。
  • -lmylib:链接 libmylib.a(省略 lib.a)。

运行程序

静态库已嵌入可执行文件,直接运行即可:

1
./myprogram

高级操作

添加新文件到静态库

1
ar rcs libmylib.a newfile.o

从静态库中删除文件

1
ar d libmylib.a file1.o

更新库中的文件

1
ar r libmylib.a file1.o  # 重新添加 file1.o

完整示例流程

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 编译源文件
gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o

# 2. 打包静态库
ar rcs libmylib.a file1.o file2.o

# 3. 使用静态库
gcc main.c -L. -lmylib -o myprogram

# 4. 运行
./myprogram

常见问题

Q1:静态库和动态库能否混合使用?

可以,但需确保符号冲突已解决。例如:

1
gcc main.c -L. -lmylib -ldynamiclib -o myprogram

Q2:如何查看可执行文件依赖的库?

对静态库编译的程序:

1
ldd myprogram  # 静态库部分不会显示,动态库会列出

Q3:为什么静态库不需要 -fPIC

因为静态库代码会被直接复制到可执行文件中,地址在链接时固定。


总结

  1. 编译 .o 文件gcc -c file1.c -o file1.o
  2. 打包静态库ar rcs lib<name>.a *.o
  3. 使用静态库gcc main.c -L. -l<name> -o program

静态库适合需要 独立部署、避免外部依赖 的场景,而动态库更适合 节省空间和多进程共享



动态库

在 Linux/Unix 系统中,动态库(Dynamic Library,也称为共享库,Shared Library)通常以 .so(Shared Object)结尾。动态库在程序运行时被加载,而不是在编译时静态链接到可执行文件中,这使得它们更节省磁盘和内存空间,并支持热更新。以下是生成和使用动态库的详细步骤。


准备源文件

假设有两个源文件:

  • file1.c
  • file2.c

示例代码

1
2
3
4
5
6
// file1.c
#include <stdio.h>

void func1() {
printf("This is func1()\n");
}
1
2
3
4
5
6
// file2.c
#include <stdio.h>

void func2() {
printf("This is func2()\n");
}

编译生成位置无关代码(PIC)

动态库需要编译为 位置无关代码(Position-Independent Code, PIC),以便在运行时加载到任意内存地址。使用 -fPIC 选项:

1
2
gcc -c -fPIC file1.c -o file1.o
gcc -c -fPIC file2.c -o file2.o
  • -fPIC:生成位置无关代码(必须加,否则链接时会报错)。

生成动态库(.so 文件)

使用 gcc-shared 选项将 .o 文件打包成动态库:

1
gcc -shared file1.o file2.o -o libmylib.so
  • -shared:告诉 gcc 生成动态库。
  • -o libmylib.so:指定输出文件名(通常格式为 lib<name>.so)。

验证动态库

查看动态库的符号(导出的函数)

1
nm -D libmylib.so

输出示例:

1
2
0000000000001110 T func1
0000000000001125 T func2
  • T 表示该符号在库中定义。

检查动态库的依赖

1
ldd libmylib.so

输出示例:

1
2
3
linux-vdso.so.1 (0x00007ffd3a3f0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8c3a3f0000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8c3a3f0000)

使用动态库

编译可执行文件并链接动态库

1
gcc main.c -L. -lmylib -o myprogram
  • -L.:告诉编译器在当前目录查找库文件。
  • -lmylib:链接 libmylib.so-l 后接库名,省略 lib.so)。

运行程序

由于动态库在运行时加载,需要确保系统能找到它:

方法 1:临时设置 LD_LIBRARY_PATH
1
2
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./myprogram
  • LD_LIBRARY_PATH 指定动态库的搜索路径。
方法 2:永久配置(推荐)

将库文件复制到标准库路径(如 /usr/local/lib),然后更新动态库缓存:

1
2
sudo cp libmylib.so /usr/local/lib
sudo ldconfig

然后直接运行:

1
./myprogram

高级用法

指定动态库版本

1
2
gcc -shared file1.o file2.o -o libmylib.so.1.0
ln -s libmylib.so.1.0 libmylib.so # 创建软链接
  • 版本号格式通常为 libname.so.major.minor

控制符号导出

在代码中使用 __attribute__((visibility("hidden"))) 隐藏符号:

1
2
3
4
// file1.c
void __attribute__((visibility("hidden"))) internal_func() {
// 该函数不会导出,仅在库内部使用
}

动态库的加载卸载(dlopen / dlclose

在程序运行时手动加载动态库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <dlfcn.h>

int main() {
void *handle = dlopen("./libmylib.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "Error: %s\n", dlerror());
return 1;
}

void (*func1)() = dlsym(handle, "func1");
func1();

dlclose(handle);
return 0;
}

编译时需要链接 -ldl

1
gcc main.c -ldl -o myprogram

总结

  1. 编译 PIC 代码gcc -c -fPIC file1.c -o file1.o
  2. 生成动态库gcc -shared *.o -o lib<name>.so
  3. 使用动态库
    • 编译时:gcc main.c -L. -l<name>
    • 运行时:确保 LD_LIBRARY_PATH 包含库路径,或复制到 /usr/local/lib 并运行 ldconfig

动态库适合需要 多进程共享、减少磁盘占用、支持热更新 的场景。