c/c++ static library

 

概念

static library(静态库)是一种在编译链接阶段就被完整合并到最终可执行文件中的代码库

它本质上是一个目标文件(.o)的归档集合, 当程序需要使用库中的功能时, 链接器(Linker)会将静态库中被引用的代码段”复制”并嵌入到最终的可执行文件中

  • Unix/Linux 环境下, 扩展名通常为 .a(Archive)

  • Windows环境下, 扩展名通常为 .lib

核心特性

  • 编译时链接

在程序编译链接阶段, 库代码就被物理嵌入到可执行文件中

  • 运行时依赖

程序运行时不需要在系统中查找或加载额外的库文件, 因为代码已经是可执行文件的一部分

  • 文件体积

如果多个可执行文件都使用了同一个静态库, 库代码会被复制到每个可执行文件中, 导致整体磁盘占用和内存占用增加

  • 版本控制

静态库没有运行时版本控制机制, 一旦库代码更新, 所有依赖该库的程序都必须重新编译链接才能生效

特性 静态库 动态库
链接时间 编译时 在运行时由操作系统加载和链接
文件大小 可执行文件包含库代码, 较大 可执行文件较小, 库文件单独
性能 无需在运行时加载, 启动速度较快 启动时需要加载, 可能稍慢
更新 更新库文件需要重新编译所有依赖程序 更新库文件后, 无需重新编译, 程序直接使用新库
依赖管理 无需在运行时寻找库文件 程序需要在运行时找到正确库文件
内存使用 每个程序包含库副本 多个程序可以共享同一个动态库内存

底层链接原理

flowchart TD
    A[链接器开始工作] --> B{扫描目标文件 main.o}
    B -->|发现未解析符号 add| C[扫描静态库 libapi.a]
    C -->|找到 add 的目标文件 api_add.o| D[将 api_add.o 加入链接集合]
    D --> E{api_add.o 是否引入新符号?}
    E -->|是| C
    E -->|否| F{main.o 还有未解析符号?}
    F -->|是| C
    F -->|否| G[生成最终可执行文件]
    
    style A fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style G fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style C fill:#fff3e0,stroke:#f57c00,stroke-width:2px

开发流程

⚠️ 避坑提示:头文件(.h / .hpp)中应尽量避免直接包含 等重型 C++ 标准库头文件, 这会导致包含该头文件的所有源文件编译变慢 应将实现细节和标准库包含放在 .cpp 中

生成

// static_api.h
#ifndef STATIC_API_H
#define STATIC_API_H

// 确保在 C++ 编译时, 支持 C 语言调用(Name Mangling 兼容)
#ifdef __cplusplus
extern "C" {
#endif

int add(int x, int y);
void hello();

#ifdef __cplusplus
}
#endif

#endif // STATIC_API_H

// static_api.cpp
#include "static_api.h"
#include <iostream> // 标准库包含放在 .cpp 中

int add(int x, int y){
    return x + y;
}

void hello(){
    std::cout << "Hello from Static Library!" << std::endl;
}

编译与打包

Linux/macOS 下, 使用编译器生成目标文件, 然后使用 ar(Archive)工具将其打包

# 1. 编译生成目标文件(.o)
clang++ -c static_api.cpp -o static_api.o

# 2. 打包生成静态库(.a)
ar rcs libstatic_api.a static_api.o

ar 命令参数解析

flowchart LR
    subgraph ar_command ["ar rcs libstatic_api.a static_api.o"]
        direction TB
        A["r(Replace)"] --> A1("将 .o 插入库中, 若已存在则替换")
        B["c(Create)"] --> B1("创建新库, 若不存在则创建, 存在则不警告")
        C["s(Index)"] --> C1("写入或更新符号索引表(ranlib), 加速链接")
        D["v(Verbose)"] -.-> D1("可选:详细输出处理过程")
    end
clang++ static_api.cpp -c -o static_api.o

ar rcv libstatic_api.a static_api.o

构建工具

在实际工程中, 几乎不会手动敲 ar 命令, 而是使用 CMake 等构建系统

以下是符合现代 CMake(Target-based)规范的写法

cmake_minimum_required(VERSION 3.16)
project(StaticLibDemo)

# 1. 定义静态库目标
add_library(static_api STATIC static_api.cpp)

# 2. 指定头文件搜索路径(PUBLIC 表示链接此库的目标也会继承此路径)
target_include_directories(static_api PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# 3. 定义可执行文件目标
add_executable(main main.cpp)

# 4. 将静态库链接到可执行文件
target_link_libraries(main PRIVATE static_api)

使用

手动链接(命令行)

// main.cpp
#include "static_api.hpp"

int main(void){
    hello();
    std::cout << add(0xA, 0xB)<< std::endl;

    return 0;
}

标准链接命令

clang++ main.cpp -L. -lstatic_api -o main

参数解析:

  • -L.: 告诉链接器在当前目录(.)下搜索库文件

如果是其他路径, 如 /usr/local/lib写为 -L/usr/local/lib

  • -lstatic_api:告诉链接器寻找名为 libstatic_api.a(或 .so)的库

注意:必须去掉 lib 前缀和 .a 后缀