静态库

当同一份实现要被多个程序使用时,每次复制源码会带来重复维护。本章把 greeting 构建成静态库,再链接进 app。

认识静态库

静态库在 Linux 上通常使用 .a 扩展名,保存已经编译好的库代码,不能直接运行。链接 app 时,链接器会把程序实际使用的库代码放进最终可执行文件。

工程目录
static-library/
├── CMakeLists.txt
├── main.cpp
└── greeting/
    ├── CMakeLists.txt
    ├── include/
    │   └── greeting.h
    └── src/
        └── greeting.cpp

查看构建规则

顶层文件不直接编译 greeting.cpp:add_subdirectory(greeting) 会让构建系统进入库目录执行其中的规则。

CMakeLists.txt
# 构建 StaticLibrary 工程:根 main.cpp 使用 greeting/ 提供的静态库。
cmake_minimum_required(VERSION 3.31)
project(StaticLibrary LANGUAGES CXX)

# 使用 C++20 作为语言标准。
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 可执行文件输出到 build/bin。
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 静态库输出到 build/lib。
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

# 编译数据库,记录每个源文件的实际编译参数。
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# 加载 greeting/CMakeLists.txt 中的静态库目标。
add_subdirectory(greeting)

# 创建 app 可执行目标。
add_executable(app main.cpp)

# 链接 greeting 静态库。
target_link_libraries(app PRIVATE greeting)

# 编译时启用 libc++ 与常用警告。
target_compile_options(app PRIVATE -stdlib=libc++ -Wall -Wextra -Werror)
# 链接时也使用 libc++。
target_link_options(app PRIVATE -stdlib=libc++)

库目录自己负责创建 greeting 目标。

greeting/CMakeLists.txt
# 构建 greeting 静态库。

# 声明 greeting 静态库目标:由 src/greeting.cpp 编译生成。
add_library(greeting STATIC src/greeting.cpp)

# include/ 加入头文件搜索路径,供使用方 include。
target_include_directories(greeting PUBLIC include)

# 编译时启用 libc++ 与常用警告。
target_compile_options(greeting PRIVATE -stdlib=libc++ -Wall -Wextra -Werror)

STATIC 生成的就是静态库产物 .a 文件,构建产物为 libgreeting.a。PUBLIC 表示使用这个库的程序也能找到 include/。target_link_libraries(app PRIVATE greeting) 告诉 CMake:构建 app 时使用这个库。

构建并运行

Ubuntu Bash(工程根目录)
# 配置工程,并生成 Ninja 使用的构建规则
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++
# 编译库和 app,并完成链接
cmake --build build
# 运行程序
./build/bin/app

输出如下:

输出
Hello, World!

构建后会得到 build/lib/libgreeting.a 和 build/bin/app。

静态库产物

Ubuntu Bash(static-library 工程根目录)
ldd build/bin/app

输出中不会出现 libgreeting.a。静态库只参与链接,需要的代码已经进入 app,运行时不再读取 .a 文件。

选择静态库

静态库能让程序少带一个运行时库文件,适合希望简化部署的场景。代价是库改变后,所有使用它的程序都要重新链接。

常见错误

链接器找不到 MakeGreeting()

先确认顶层 CMakeLists.txt 包含 target_link_libraries(app PRIVATE greeting)。补回后重新配置并构建:

Ubuntu Bash(工程根目录)
# 重新配置,让 CMake 重新读取链接关系
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++
# 编译库和 app,并完成链接
cmake --build build

自测问题

先不要展开答案,试着自己把回答完整说出来,再展开对照。说不出来的部分,回到对应小节对照正文。

  1. 为什么 ldd build/bin/app 不会列出 libgreeting.a?

    查看答案

    静态库只在链接阶段提供目标代码,链接器会把被使用的代码复制进 app。程序运行时不再依赖原来的 .a 文件。

本章回顾

你已经能构建并链接静态库。下一章会改成动态库,观察程序运行时如何查找库文件。