动态库与静态库
库的创建与使用
概述
库(Library)是预编译的目标代码集合,用于代码复用和模块化开发。静态库(.a/.lib)在编译时被链接到可执行文件中,动态库(.so/.dll)在运行时加载。理解库的创建和使用是C项目开发的核心技能,也是理解软件分发和依赖管理的基础。
基础概念
静态库 vs 动态库
| 特性 | 静态库 | 动态库 |
|---|---|---|
| 链接时机 | 编译时 | 运行时 |
| 文件扩展名 | Linux: .a, Windows: .lib | Linux: .so, Windows: .dll |
| 体积影响 | 增大可执行文件 | 不影响可执行文件大小 |
| 更新方式 | 需重新编译 | 替换库文件即可 |
| 内存使用 | 每个进程一份副本 | 多个进程共享 |
| 启动速度 | 较快 | 稍慢(需加载) |
库的命名约定
- 静态库:
libname.a(Linux)、name.lib(Windows) - 动态库:
libname.so(Linux)、name.dll(Windows) - 链接时使用
-lname,编译器自动查找libname.a或libname.so
快速上手
创建静态库
# 步骤一:编译源文件为目标文件
gcc -c math_utils.c -o math_utils.o
gcc -c string_utils.c -o string_utils.o
# 步骤二:使用 ar 创建静态库
ar rcs libmyutils.a math_utils.o string_utils.o
# 步骤三:使用静态库编译程序
gcc main.c -L. -lmyutils -o program
创建动态库
# 步骤一:编译为位置无关代码
gcc -fPIC -c math_utils.c -o math_utils.o
gcc -fPIC -c string_utils.c -o string_utils.o
# 步骤二:创建动态库
gcc -shared -o libmyutils.so math_utils.o string_utils.o
# 步骤三:使用动态库编译程序
gcc main.c -L. -lmyutils -o program
# 步骤四:运行时指定库搜索路径
LD_LIBRARY_PATH=. ./program
详细用法
静态库操作
# 查看静态库中的目标文件
ar -t libmyutils.a
# 查看静态库中函数的符号表
nm libmyutils.a
# 向静态库添加新的目标文件
ar rcs libmyutils.a new_module.o
# 从静态库删除目标文件
ar d libmyutils.a old_module.o
# 提取静态库中的目标文件
ar x libmyutils.a math_utils.o
动态库版本管理
# 创建带版本号的动态库
gcc -shared -Wl,-soname,libmyutils.so.1 -o libmyutils.so.1.0.0 math_utils.o string_utils.o
# 创建符号链接
ln -sf libmyutils.so.1.0.0 libmyutils.so.1 # soname 链接
ln -sf libmyutils.so.1 libmyutils.so # 开发链接
# 安装到系统目录
sudo cp libmyutils.so.1.0.0 /usr/local/lib/
sudo ldconfig # 更新动态链接器缓存
头文件设计
// myutils.h - 库的公共头文件
#ifndef MYUTILS_H
#define MYUTILS_H
// 导出宏:控制符号可见性
#ifdef _WIN32
#ifdef MYUTILS_EXPORTS
#define MYUTILS_API __declspec(dllexport)
#else
#define MYUTILS_API __declspec(dllimport)
#endif
#else
#define MYUTILS_API __attribute__((visibility("default")))
#endif
// 数学工具函数
MYUTILS_API int add(int a, int b);
MYUTILS_API int multiply(int a, int b);
// 字符串工具函数
MYUTILS_API size_t count_words(const char *str);
MYUTILS_API int is_palindrome(const char *str);
#endif // MYUTILS_H
实现文件
// math_utils.c
#include "myutils.h"
MYUTILS_API int add(int a, int b) {
return a + b;
}
MYUTILS_API int multiply(int a, int b) {
return a * b;
}
// string_utils.c
#include "myutils.h"
#include <string.h>
#include <ctype.h>
MYUTILS_API size_t count_words(const char *str) {
if (!str) return 0;
size_t count = 0;
int in_word = 0;
while (*str) {
if (isspace((unsigned char)*str)) {
in_word = 0;
} else if (!in_word) {
in_word = 1;
count++;
}
str++;
}
return count;
}
MYUTILS_API int is_palindrome(const char *str) {
if (!str) return 0;
size_t len = strlen(str);
for (size_t i = 0; i < len / 2; i++) {
if (str[i] != str[len - 1 - i]) return 0;
}
return 1;
}
常见场景
场景一:使用 CMake 构建库
cmake_minimum_required(VERSION 3.20)
project(MyUtils C)
set(CMAKE_C_STANDARD 17)
# 构建静态库
add_library(myutils_static STATIC
math_utils.c
string_utils.c
)
target_include_directories(myutils_static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
# 构建动态库
add_library(myutils_shared SHARED
math_utils.c
string_utils.c
)
target_include_directories(myutils_shared PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(myutils_shared PROPERTIES
OUTPUT_NAME "myutils"
VERSION 1.0.0
SOVERSION 1
)
# Windows 导出定义
if(WIN32)
target_compile_definitions(myutils_shared PRIVATE MYUTILS_EXPORTS)
endif()
场景二:插件系统
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
typedef HMODULE lib_handle;
#define LOAD_LIB(path) LoadLibraryA(path)
#define GET_SYM(handle, name) (void *)GetProcAddress(handle, name)
#define CLOSE_LIB(handle) FreeLibrary(handle)
#else
#include <dlfcn.h>
typedef void *lib_handle;
#define LOAD_LIB(path) dlopen(path, RTLD_LAZY)
#define GET_SYM(handle, name) dlsym(handle, name)
#define CLOSE_LIB(handle) dlclose(handle)
#endif
// 插件接口
typedef struct {
const char *name;
void (*init)(void);
void (*execute)(const char *input);
void (*cleanup)(void);
} Plugin;
Plugin *load_plugin(const char *path) {
lib_handle handle = LOAD_LIB(path);
if (!handle) return NULL;
// 获取插件创建函数
Plugin *(*create_plugin)(void) = GET_SYM(handle, "create_plugin");
if (!create_plugin) {
CLOSE_LIB(handle);
return NULL;
}
return create_plugin();
}
int main(void) {
Plugin *plugin = load_plugin("./plugin.so");
if (plugin) {
printf("插件: %s\n", plugin->name);
if (plugin->init) plugin->init();
if (plugin->execute) plugin->execute("测试数据");
if (plugin->cleanup) plugin->cleanup();
}
return 0;
}
场景三:运行时加载动态库
#include <stdio.h>
#include <dlfcn.h>
int main(void) {
// 加载数学库
void *handle = dlopen("libm.so.6", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "加载失败: %s\n", dlerror());
return 1;
}
// 获取 sqrt 函数
double (*sqrt_func)(double) = dlsym(handle, "sqrt");
if (!sqrt_func) {
fprintf(stderr, "查找符号失败: %s\n", dlerror());
dlclose(handle);
return 1;
}
// 调用函数
printf("sqrt(2.0) = %f\n", sqrt_func(2.0));
// 关闭库
dlclose(handle);
return 0;
}
编译:gcc -rdynamic main.c -ldl -o program
注意事项
符号可见性
动态库默认导出所有全局符号,可能导致符号冲突。建议使用 -fvisibility=hidden 编译选项,只导出显式标记的符号:
gcc -fvisibility=hidden -fPIC -shared -o libmyutils.so math_utils.c string_utils.c
位置无关代码(PIC)
动态库必须使用 -fPIC 编译,否则在某些架构上无法正常工作:
# 正确
gcc -fPIC -c math_utils.c
# 错误(可能导致动态库加载失败)
gcc -c math_utils.c
运行时库搜索路径
程序运行时需要找到动态库,有三种方式:
# 方式一:设置环境变量
export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH
# 方式二:安装到系统目录
sudo cp libmyutils.so /usr/local/lib/
sudo ldconfig
# 方式三:编译时指定运行时路径
gcc -Wl,-rpath,/path/to/lib main.c -L/path/to/lib -lmyutils -o program
进阶用法
使用 CMake 安装和导出库
# 安装库和头文件
install(TARGETS myutils_static myutils_shared
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install(FILES myutils.h DESTINATION include)
# 导出CMake配置,供其他项目使用
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/myutilsConfigVersion.cmake"
VERSION 1.0.0
COMPATIBILITY SameMajorVersion
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/myutilsConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/myutilsConfigVersion.cmake"
DESTINATION lib/cmake/myutils
)
使用 dlopen 实现热更新
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>
typedef void (*process_func)(const char *input);
int main(void) {
while (1) {
// 每次循环重新加载库,实现热更新
void *handle = dlopen("./libworker.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "加载失败: %s\n", dlerror());
sleep(2);
continue;
}
process_func process = dlsym(handle, "process");
if (process) {
process("输入数据");
}
dlclose(handle);
sleep(5); // 每5秒检查更新
}
return 0;
}
符号版本控制
// 版本脚本文件: myutils.map
// 控制导出的符号和版本
{
global:
add;
multiply;
count_words;
is_palindrome;
local:
*; // 隐藏其他所有符号
};
编译时使用版本脚本:
gcc -fPIC -shared -Wl,--version-script=myutils.map \
-o libmyutils.so math_utils.c string_utils.c