外观
08.Linux下静态库与动态库详解
约 2474 字大约 8 分钟
动态库C个人随笔
2025-12-12
静态库
创建静态库
1. 准备源代码
math_operations.h - 头文件
#ifndef MATH_OPERATIONS_H
#define MATH_OPERATIONS_H
// 基础数学运算
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);
// 高级数学运算
int factorial(int n);
int gcd(int a, int b);
int lcm(int a, int b);
#endifbasic_math.c - 基础运算实现
#include "math_operations.h"
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
double divide(int a, int b) {
if (b == 0) {
return 0.0;
}
return (double)a / b;
}advanced_math.c - 高级运算实现
#include "math_operations.h"
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
int lcm(int a, int b) {
return (a * b) / gcd(a, b);
}2. 编译为目标文件
# 编译为.o目标文件
gcc -c basic_math.c advanced_math.c -Wall -Wextra -O2
# 查看生成的文件
ls *.o
# 输出: advanced_math.o basic_math.o3. 创建静态库
# 使用ar命令创建静态库
# r: 替换/插入文件到归档中
# c: 创建归档(如果不存在)
# s: 创建索引(加速链接)
ar rcs libmath.a basic_math.o advanced_math.o
# 查看库文件信息
file libmath.a
# 输出: libmath.a: current ar archive
ls -lh libmath.a
# 显示库文件大小使用静态库
main.c - 测试程序
#include <stdio.h>
#include <stdlib.h>
#include "math_operations.h"
int main() {
int a = 24, b = 36;
printf("=== 数学运算测试 ===\n");
printf("加法: %d + %d = %d\n", a, b, add(a, b));
printf("减法: %d - %d = %d\n", a, b, subtract(a, b));
printf("乘法: %d * %d = %d\n", a, b, multiply(a, b));
printf("除法: %d / %d = %.2f\n", a, b, divide(a, b));
printf("阶乘: %d! = %d\n", 5, factorial(5));
printf("最大公约数: gcd(%d, %d) = %d\n", a, b, gcd(a, b));
printf("最小公倍数: lcm(%d, %d) = %d\n", a, b, lcm(a, b));
return 0;
}编译链接静态库
# 方法1:直接指定库文件
gcc -o static_app main.c libmath.a -Wall
# 方法2:使用-L和-l选项(更规范)
gcc -o static_app main.c -L. -lmath -Wall
# 运行程序
./static_app
# 查看可执行文件信息
ldd static_app # 静态链接,不依赖外部库
file static_app
size static_app # 查看各段大小静态库内部查看
# 查看库中包含的目标文件
ar t libmath.a
# 输出: basic_math.o advanced_math.o
# 查看库的详细信息
ar tv libmath.a
# 显示: 每个目标文件的大小、权限、时间戳
# 提取库中的目标文件
ar x libmath.a
# 提取出 basic_math.o 和 advanced_math.o
# 查看符号表(函数和变量)
nm libmath.a
# 显示所有符号,T表示已定义的文本(代码)符号
# 查看反汇编(需要objdump)
objdump -d libmath.a | less动态库
创建动态库
1. 编译为位置无关代码
# -fPIC: 生成位置无关代码(Position Independent Code)
# 这是创建动态库的必要条件
gcc -c -fPIC basic_math.c advanced_math.c -Wall -Wextra -O22. 创建动态库
# -shared: 创建共享库
# -o: 指定输出文件名
gcc -shared -o libmath.so basic_math.o advanced_math.o
# 或者一步完成编译和链接
gcc -shared -fPIC -o libmath.so basic_math.c advanced_math.c -Wall
# 查看动态库信息
file libmath.so
# 输出: libmath.so: ELF 64-bit LSB shared object...
ldd libmath.so # 查看动态库的依赖
readelf -d libmath.so # 查看动态段信息3. 为动态库创建符号链接(推荐)
# 版本化管理
ln -sf libmath.so libmath.so.1 # 主版本号
ln -sf libmath.so.1 libmath.so.1.0 # 完整版本号
# 查看结果
ls -la libmath.so*使用动态库
编译时链接动态库
# 编译链接(方式1:直接链接)
gcc -o dynamic_app main.c -L. -lmath -Wall
# 尝试运行(可能会失败,因为动态库不在系统路径)
./dynamic_app
# 可能的错误: ./dynamic_app: error while loading shared libraries: libmath.so: cannot open shared object file
# 解决方法1:设置LD_LIBRARY_PATH环境变量
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./dynamic_app
# 解决方法2:在编译时指定rpath(相对路径)
gcc -o dynamic_app main.c -L. -lmath -Wl,-rpath,'$ORIGIN' -Wall
# $ORIGIN 表示可执行文件所在目录
# 解决方法3:在编译时指定rpath(绝对路径)
gcc -o dynamic_app main.c -L. -lmath -Wl,-rpath,/usr/local/lib -Wall查看动态库依赖
# 查看程序的动态库依赖
ldd dynamic_app
# 输出示例:
# linux-vdso.so.1 (0x00007ffd...)
# libmath.so => ./libmath.so (0x00007f...)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
# /lib64/ld-linux-x86-64.so.2 (0x00007f...)
# 查看动态符号表
nm -D libmath.so
readelf -s libmath.so动态库加载机制
动态链接器搜索路径
动态链接器按以下顺序搜索库文件:
LD_PRELOAD环境变量指定的库DT_RPATH段中的路径(编译时指定)LD_LIBRARY_PATH环境变量/etc/ld.so.cache中的缓存路径- 默认路径:
/lib,/usr/lib,/usr/local/lib
动态库配置文件
# 查看当前配置
cat /etc/ld.so.conf
# 通常包含子目录配置
ls /etc/ld.so.conf.d/
# 添加自定义库路径
sudo echo "/usr/local/lib" > /etc/ld.so.conf.d/myapp.conf
sudo ldconfig # 更新缓存
# 验证库是否被系统找到
ldconfig -p | grep libmath运行时动态加载
dl_demo.c - 动态加载示例
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h> // 动态加载头文件
int main() {
void *handle;
char *error;
// 函数指针声明
int (*add_ptr)(int, int);
int (*factorial_ptr)(int);
int (*gcd_ptr)(int, int);
printf("=== 动态库运行时加载演示 ===\n\n");
// 1. 打开动态库
printf("1. 加载动态库...\n");
handle = dlopen("./libmath.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "加载失败: %s\n", dlerror());
exit(EXIT_FAILURE);
}
printf(" 动态库加载成功!\n\n");
// 清除之前的错误
dlerror();
// 2. 获取函数地址
printf("2. 查找函数地址...\n");
// 获取加法函数
add_ptr = dlsym(handle, "add");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "找不到add函数: %s\n", error);
dlclose(handle);
exit(EXIT_FAILURE);
}
printf(" add函数地址: %p\n", add_ptr);
// 获取阶乘函数
factorial_ptr = dlsym(handle, "factorial");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "找不到factorial函数: %s\n", error);
dlclose(handle);
exit(EXIT_FAILURE);
}
printf(" factorial函数地址: %p\n", factorial_ptr);
// 获取最大公约数函数
gcd_ptr = dlsym(handle, "gcd");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "找不到gcd函数: %s\n", error);
dlclose(handle);
exit(EXIT_FAILURE);
}
printf(" gcd函数地址: %p\n\n", gcd_ptr);
// 3. 使用动态加载的函数
printf("3. 执行函数...\n");
printf(" 10 + 20 = %d\n", add_ptr(10, 20));
printf(" 5! = %d\n", factorial_ptr(5));
printf(" gcd(48, 18) = %d\n\n", gcd_ptr(48, 18));
// 4. 关闭动态库
printf("4. 卸载动态库...\n");
if (dlclose(handle) != 0) {
fprintf(stderr, "卸载失败: %s\n", dlerror());
exit(EXIT_FAILURE);
}
printf(" 动态库卸载成功!\n");
return EXIT_SUCCESS;
}编译运行动态加载示例
# 编译(需要链接dl库)
gcc -o dl_demo dl_demo.c -ldl -Wall
# 运行
./dl_demo
# 带调试信息运行
LD_DEBUG=libs ./dl_demo 2>&1 | head -20实战对比与选择
对比示例
# 创建两个相同的程序,一个静态链接,一个动态链接
gcc -o app_static main.c -static -L. -lmath -Wall
gcc -o app_dynamic main.c -L. -lmath -Wl,-rpath,'$ORIGIN' -Wall
# 对比文件大小
echo "=== 文件大小对比 ==="
ls -lh app_* libmath.so
# 对比启动时间(简单测试)
echo -e "\n=== 启动时间对比(简单测试)==="
time ./app_static > /dev/null
time ./app_dynamic > /dev/null
# 对比内存使用
echo -e "\n=== 内存映射对比 ==="
echo "静态链接:"
pmap $(pgrep -f app_static) | head -10 2>/dev/null || echo "进程已结束"
echo -e "\n动态链接:"
pmap $(pgrep -f app_dynamic) | head -10 2>/dev/null || echo "进程已结束"选择指南
| 场景 | 推荐选择 | 理由 |
|---|---|---|
| 嵌入式系统 | 静态库 | 资源受限,部署简单 |
| 命令行工具 | 静态库 | 独立分发,无依赖 |
| 系统级软件 | 动态库 | 共享内存,便于更新 |
| 桌面应用 | 动态库 | 节省空间,模块化 |
| 服务器程序 | 动态库 | 热更新,内存共享 |
| 开发调试 | 动态库 | 快速迭代,无需重编 |
高级技巧与最佳实践
1. 版本控制
# 带版本号的动态库
gcc -shared -fPIC -Wl,-soname,libmath.so.1 -o libmath.so.1.0.0 *.c
# 创建符号链接
ln -sf libmath.so.1.0.0 libmath.so.1
ln -sf libmath.so.1 libmath.so
# 编译时指定版本
gcc -o app main.c -L. -lmath -Wl,-rpath,'$ORIGIN'2. 优化建议
# 编译优化
gcc -shared -fPIC -o libmath.so *.c \
-Wall -Wextra -Werror \
-O2 -g \
-Wl,-Bsymbolic \
-Wl,--as-needed
# 减少符号表大小
strip --strip-unneeded libmath.so
# 检查未定义符号
nm -u libmath.so3. Makefile示例
Makefile
CC = gcc
CFLAGS = -Wall -Wextra -O2
LDFLAGS =
AR = ar
ARFLAGS = rcs
# 文件定义
SRCS = basic_math.c advanced_math.c
OBJS = $(SRCS:.c=.o)
HEADER = math_operations.h
TARGET_STATIC = libmath.a
TARGET_DYNAMIC = libmath.so
EXAMPLE = main.c
EXAMPLE_TARGET = math_app
.PHONY: all static dynamic clean test
all: static dynamic
# 静态库
static: $(TARGET_STATIC)
$(TARGET_STATIC): $(OBJS)
$(AR) $(ARFLAGS) $@ $^
# 动态库
dynamic: CFLAGS += -fPIC
dynamic: $(TARGET_DYNAMIC)
$(TARGET_DYNAMIC): $(OBJS)
$(CC) -shared -o $@ $^ $(LDFLAGS)
# 目标文件
%.o: %.c $(HEADER)
$(CC) $(CFLAGS) -c $< -o $@
# 示例程序
example_static: static
$(CC) $(EXAMPLE) -o $(EXAMPLE_TARGET)_static -L. -lmath $(CFLAGS)
example_dynamic: dynamic
$(CC) $(EXAMPLE) -o $(EXAMPLE_TARGET)_dynamic -L. -lmath -Wl,-rpath,'$$ORIGIN' $(CFLAGS)
# 测试
test: example_dynamic
LD_LIBRARY_PATH=. ./$(EXAMPLE_TARGET)_dynamic
# 清理
clean:
rm -f $(OBJS) $(TARGET_STATIC) $(TARGET_DYNAMIC) $(EXAMPLE_TARGET)_* *.so.*
# 安装
install: dynamic
sudo cp $(TARGET_DYNAMIC) /usr/local/lib/
sudo cp $(HEADER) /usr/local/include/
sudo ldconfig
# 卸载
uninstall:
sudo rm -f /usr/local/lib/$(TARGET_DYNAMIC) /usr/local/include/$(notdir $(HEADER))
sudo ldconfig4. 调试技巧
# 查看动态库加载过程
LD_DEBUG=all ./dynamic_app 2>&1 | grep -E "(init|fini|binding)"
# 查看内存泄漏(valgrind)
valgrind --leak-check=full --show-leak-kinds=all ./dynamic_app
# 使用gdb调试动态库
gdb ./dynamic_app
(gdb) set environment LD_LIBRARY_PATH=.
(gdb) break add
(gdb) run总结
核心要点
静态库(.a):
- 编译时链接,成为可执行文件的一部分
- 使用
ar命令创建 - 部署简单,性能好,但体积大
动态库(.so):
- 运行时加载,可被多个进程共享
- 使用
gcc -shared -fPIC创建 - 节省内存,便于更新,但部署复杂
最佳实践建议
- 开发阶段:使用动态库便于调试和快速迭代
- 发布阶段:根据目标环境选择合适的库类型
- 版本管理:为动态库使用合理的版本号
- 路径管理:合理使用
rpath或LD_LIBRARY_PATH - 符号控制:使用静态链接隐藏内部符号
命令速查
| 任务 | 命令 |
|---|---|
| 创建静态库 | ar rcs libname.a *.o |
| 创建动态库 | gcc -shared -fPIC -o libname.so *.o |
| 查看库内容 | ar t libname.a, nm libname.so |
| 链接静态库 | gcc -o app main.c -L. -lname |
| 链接动态库 | gcc -o app main.c -L. -lname -Wl,-rpath,path |
| 查看依赖 | ldd app, readelf -d libname.so |
| 更新库缓存 | sudo ldconfig |
