Linux动态库和静态库
- Linux动态库和静态库
- 动静态库的基本原理
- 可执行程序的生成过程
- 动静态库的本质
- 认识动静态库
- 背后的库支持
- 动静态库的命名
- 静态链接示例
- 动静态库各自的特征
- 静态库
- 动态库
- 静态库的打包与使用
- 示例文件
- 打包
- 1. 生成目标文件
- 2. 打包静态库
- 3. 组织文件
- 使用 Makefile
- 使用
- 方法一:使用选项
- 方法二:拷贝到系统路径
- 动态库的打包与使用
- 打包
- 1. 生成目标文件
- 2. 打包动态库
- 3. 组织文件
- 使用 Makefile
- 使用
- 编译
- 运行时问题
- 解决方法
- 总结
Linux动态库和静态库
在Linux开发中,动态库和静态库是代码复用和程序构建的重要工具。无论是编写小型工具还是大型项目,理解动静态库的原理和使用方法都能极大提高开发效率。本文将从基本原理出发,逐步带您认识动静态库,分析它们的特征,并通过具体示例演示静态库和动态库的打包与使用过程。让我们开始吧!
动静态库的基本原理
可执行程序的生成过程
要理解动静态库的本质,首先需要了解源代码如何变成可执行程序。在Linux下,这一过程分为四个步骤:
- 预处理:处理头文件展开、去注释、宏替换和条件编译,生成
.i
文件。例如,#include
会被替换为头文件内容。 - 编译:进行词法分析、语法分析、语义分析和符号汇总,将代码翻译成汇编指令,生成
.s
文件。 - 汇编:将汇编指令转换为二进制机器码,生成目标文件
.o
。 - 链接:将多个
.o
文件链接起来,生成最终的可执行程序。
假设有五个文件:test1.c
、test2.c
、test3.c
、test4.c
和 main1.c
,要生成可执行程序,我们需要:
- 分别编译生成
test1.o
、test2.o
、test3.o
、test4.o
和main1.o
。 - 将这些目标文件链接,生成最终程序。
如果另一个项目需要用 test1.c
到 test4.c
与 main2.c
生成新程序,过程相同。但如果这些文件频繁复用,每次都重新编译会很麻烦。这时,我们可以将 test1.o
到 test4.o
打包成一个“库”,供不同项目直接链接使用。
动静态库的本质
动静态库本质上是目标文件(.o
)的集合,是可执行程序的“半成品”。它们不包含主函数(main
),只提供函数或方法的实现,供其他程序调用。库的出现解决了代码复用问题,避免了重复编译的繁琐。
认识动静态库
让我们通过一个简单程序认识动静态库:
#include <stdio.h>
int main()
{
printf("hello world\n"); // 调用库函数
return 0;
}
编译并运行:
gcc -o mytest mytest.c
./mytest
输出:hello world
背后的库支持
这个程序能输出 hello world
,是因为 gcc
在链接时自动引入了 C 标准库。可以用 ldd
查看依赖:
ldd mytest
输出示例:
linux-vdso.so.1 => (0x00007fff5f5ff000)
libc.so.6 => /lib64/libc.so.6 (0x00007f9c8e5b0000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9c8e9b4000)
其中,libc.so.6
是 C 标准动态库的软链接。查看其真实文件:
ls -l /lib64/libc.so.6
输出:libc.so.6 -> libc-2.17.so
进一步检查文件类型:
file /lib64/libc-2.17.so
输出:ELF 64-bit LSB shared object, x86-64, ...
这表明 libc-2.17.so
是一个动态共享库(.so
后缀)。
动静态库的命名
- Linux:
- 动态库:
.so
(shared object),如libc.so.6
。 - 静态库:
.a
(archive),如libm.a
。
- 动态库:
- Windows:
- 动态库:
.dll
(dynamic link library)。 - 静态库:
.lib
。
- 动态库:
库名规则:去掉前缀 lib
和后缀(.so
或 .a
)及版本号,剩下的是库名。例如,libc.so.6
的库名是 c
。
静态链接示例
默认情况下,gcc
使用动态链接。若要静态链接,添加 -static
:
gcc -o mytest-s mytest.c -static
检查依赖:
ldd mytest-s
输出:not a dynamic executable
静态链接的可执行文件不依赖动态库,体积明显更大:
ls -lh mytest mytest-s
可能输出:
mytest
:几十 KB(动态链接)。mytest-s
:几 MB(静态链接)。
动静态库各自的特征
静态库
- 链接方式:编译时将库代码复制到可执行文件中。
- 运行时:不依赖外部库。
- 优点:独立性强,可单独运行。
- 缺点:文件体积大;多个程序加载相同静态库时,内存中会有重复代码,浪费资源。
动态库
- 链接方式:运行时才加载库代码,可执行文件中只包含函数入口地址表。
- 运行时:由操作系统从磁盘加载到内存,多个程序共享同一份代码。
- 优点:节省磁盘和内存空间,多个程序共享库时效率高。
- 缺点:依赖动态库,若库缺失则无法运行。
静态库的打包与使用
示例文件
我们用以下文件演示:
add.h
:#pragma once extern int my_add(int x, int y);
add.c
:#include "add.h" int my_add(int x, int y) { return x + y; }
sub.h
:#pragma once extern int my_sub(int x, int y);
sub.c
:#include "sub.h" int my_sub(int x, int y) { return x - y; }
打包
1. 生成目标文件
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
-c
:只编译,不链接,生成.o
文件。
2. 打包静态库
ar -rc libcal.a add.o sub.o
ar
:归档工具。-r
:替换已有文件。-c
:创建新库。libcal.a
:生成静态库。
验证:
ar -tv libcal.a
输出示例:
rw-r--r-- 0/0 1234 Mar 19 12:34 2025 add.o
rw-r--r-- 0/0 1234 Mar 19 12:34 2025 sub.o
3. 组织文件
mkdir -p mathlib/include mathlib/lib
mv add.h sub.h mathlib/include/
mv libcal.a mathlib/lib/
目录结构:
mathlib/
├── include/
│ ├── add.h
│ └── sub.h
└── lib/
└── libcal.a
使用 Makefile
CC = gcc
AR = ar
CFLAGS = -c
TARGET = libcal.a
OBJS = add.o sub.o
all: $(TARGET)
add.o: add.c add.h
$(CC) $(CFLAGS) add.c -o add.o
sub.o: sub.c sub.h
$(CC) $(CFLAGS) sub.c -o sub.o
$(TARGET): $(OBJS)
$(AR) -rc $(TARGET) $(OBJS)
output:
mkdir -p mathlib/include mathlib/lib
cp *.h mathlib/include/
cp $(TARGET) mathlib/lib/
clean:
rm -f *.o $(TARGET) mathlib -r
.PHONY: all output clean
make
:生成库。make output
:组织文件。
使用
测试程序 main.c
:
#include <stdio.h>
#include <add.h>
int main()
{
int x = 20, y = 10;
int z = my_add(x, y);
printf("%d + %d = %d\n", x, y, z);
return 0;
}
方法一:使用选项
gcc main.c -I./mathlib/include -L./mathlib/lib -lcal -o main
-I
:头文件路径。-L
:库文件路径。-l
:指定库名(cal
)。
运行:
./main
输出:20 + 10 = 30
方法二:拷贝到系统路径
sudo cp mathlib/include/* /usr/include/
sudo cp mathlib/lib/libcal.a /lib64/
gcc main.c -lcal -o main
- 注意:仍需
-lcal
指定库名。
动态库的打包与使用
打包
1. 生成目标文件
gcc -c -fPIC add.c -o add.o
gcc -c -fPIC sub.c -o sub.o
-fPIC
:生成位置无关码,动态库必需。
2. 打包动态库
gcc -shared -o libcal.so add.o sub.o
-shared
:生成共享库。
3. 组织文件
mkdir -p mlib/include mlib/lib
mv add.h sub.h mlib/include/
mv libcal.so mlib/lib/
使用 Makefile
CC = gcc
CFLAGS = -c -fPIC
TARGET = libcal.so
OBJS = add.o sub.o
all: $(TARGET)
add.o: add.c add.h
$(CC) $(CFLAGS) add.c -o add.o
sub.o: sub.c sub.h
$(CC) $(CFLAGS) sub.c -o sub.o
$(TARGET): $(OBJS)
$(CC) -shared -o $(TARGET) $(OBJS)
output:
mkdir -p mlib/include mlib/lib
cp *.h mlib/include/
cp $(TARGET) mlib/lib/
clean:
rm -f *.o $(TARGET) mlib -r
.PHONY: all output clean
使用
编译
gcc main.c -I./mlib/include -L./mlib/lib -lcal -o main
运行时问题
运行 ./main
可能报错:
./main: error while loading shared libraries: libcal.so: cannot open shared object file
检查依赖:
ldd main
输出:libcal.so => not found
解决方法
-
拷贝到系统路径:
sudo cp mlib/lib/libcal.so /lib64/ ./main
-
设置 LD_LIBRARY_PATH:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/user/mlib/lib ./main
-
配置 /etc/ld.so.conf.d/:
echo "/home/user/mlib/lib" > mylib.conf sudo mv mylib.conf /etc/ld.so.conf.d/ sudo ldconfig ./main
ldconfig
:更新动态库缓存。
总结
- 静态库:打包用
ar
,生成.a
文件,编译时嵌入代码,独立性强但体积大。 - 动态库:用
gcc -shared
,生成.so
文件,运行时加载,节省空间但依赖库文件。 - 使用差异:静态库无需运行时配置,动态库需确保库路径可访问。
通过本文,您应该已经掌握了Linux下动静态库的原理和实践。希望这些知识能在您的开发中派上用场!