本文介绍了Linux环境下如何生成和使用静态库和动态库。
动态库 vs 静态库
特性 |
动态库(.so) |
静态库(.a) |
链接方式 |
运行时动态加载 |
编译时直接嵌入可执行文件 |
文件大小 |
可执行文件较小 |
可执行文件较大 |
更新方式 |
替换 .so 文件即可生效 |
需重新编译程序 |
加载速度 |
稍慢(需运行时加载) |
更快(代码已在二进制中) |
适用场景 |
多进程共享、热更新 |
独立部署、无依赖 |
静态库
在 Linux/Unix 系统中,静态库(Static
Library)是一种包含多个目标文件(.o
文件)的归档文件,通常以 .a
结尾(Archive)。静态库在编译时会被直接链接到可执行文件中,使得程序运行时不再依赖外部库文件。以下是生成静态库的详细步骤:
准备源文件
假设有两个源文件:
示例代码
1 2 3 4 5 6
| #include <stdio.h>
void func1() { printf("This is func1()\n"); }
|
1 2 3 4 5 6
| #include <stdio.h>
void func2() { printf("This is func2()\n"); }
|
编译生成目标文件(.o
文件)
使用 gcc
或 clang
编译源文件,生成位置无关的目标文件:
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 2 3 4 5
| file1.o: 0000000000000000 T func1
file2.o: 0000000000000000 T func2
|
使用静态库
编译并链接静态库
1
| gcc main.c -L. -lmylib -o myprogram
|
-L.
:指定库的搜索路径(.
表示当前目录)。
-lmylib
:链接
libmylib.a
(省略 lib
和
.a
)。
运行程序
静态库已嵌入可执行文件,直接运行即可:
高级操作
添加新文件到静态库
1
| ar rcs libmylib.a newfile.o
|
从静态库中删除文件
更新库中的文件
完整示例流程
1 2 3 4 5 6 7 8 9 10 11 12
| gcc -c file1.c -o file1.o gcc -c file2.c -o file2.o
ar rcs libmylib.a file1.o file2.o
gcc main.c -L. -lmylib -o myprogram
./myprogram
|
常见问题
Q1:静态库和动态库能否混合使用?
可以,但需确保符号冲突已解决。例如:
1
| gcc main.c -L. -lmylib -ldynamiclib -o myprogram
|
Q2:如何查看可执行文件依赖的库?
对静态库编译的程序:
Q3:为什么静态库不需要
-fPIC
?
因为静态库代码会被直接复制到可执行文件中,地址在链接时固定。
总结
- 编译
.o
文件:gcc -c file1.c -o file1.o
- 打包静态库:
ar rcs lib<name>.a *.o
- 使用静态库:
gcc main.c -L. -l<name> -o program
静态库适合需要 独立部署、避免外部依赖
的场景,而动态库更适合 节省空间和多进程共享。
动态库
在 Linux/Unix 系统中,动态库(Dynamic Library,也称为共享库,Shared
Library)通常以 .so
(Shared
Object)结尾。动态库在程序运行时被加载,而不是在编译时静态链接到可执行文件中,这使得它们更节省磁盘和内存空间,并支持热更新。以下是生成和使用动态库的详细步骤。
准备源文件
假设有两个源文件:
示例代码
1 2 3 4 5 6
| #include <stdio.h>
void func1() { printf("This is func1()\n"); }
|
1 2 3 4 5 6
| #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 2
| 0000000000001110 T func1 0000000000001125 T func2
|
检查动态库的依赖
输出示例:
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 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
| 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
|
总结
- 编译 PIC
代码:
gcc -c -fPIC file1.c -o file1.o
- 生成动态库:
gcc -shared *.o -o lib<name>.so
- 使用动态库:
- 编译时:
gcc main.c -L. -l<name>
- 运行时:确保
LD_LIBRARY_PATH
包含库路径,或复制到
/usr/local/lib
并运行 ldconfig
。
动态库适合需要 多进程共享、减少磁盘占用、支持热更新
的场景。