前置知识: C

动态库与静态库

00:00
3 min Intermediate 2026/6/14

库的创建与使用

概述

(Library)是预编译目标代码集合,用于代码复用和模块化开发。静态库(.a/.lib)在编译时被链接到可执行文件中,动态库(.so/.dll)在运行时加载。理解库的创建和使用是C项目开发的核心技能,也是理解软件分发和依赖管理的基础。

基础概念

静态库 vs 动态库

特性静态动态
链接时机编译时运行时
文件扩展名Linux: .a, Windows: .libLinux: .so, Windows: .dll
体积影响增大可执行文件影响执行文件大小
更新方式需重新编译替换文件即可
内存使用每个进程一份副本进程共享
启动较快稍慢(需加载)

库的命名约定

  • 静态库:libname.a(Linux)、name.lib(Windows)
  • 动态库:libname.so(Linux)、name.dll(Windows)
  • 链接时使用 -lname编译器自动查找 libname.alibname.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

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式