【学习笔记】CMake 笔记

项目文件过多,互相 include 关系盘根错节,每次编译得到一个可执行文件时间很长,做微小修改后总是需要重新编译。于是出现了 make,通过 Makefile,对修改的文件进行检测,只重新编译/链接被修改过的文件。学习 Makefile 的书写和修改成本很高,于是出现了 CMake,提供固定的接口(语法规则)来实现 Makefile 的生成。

本文通过某项目的所有 cmake 文件中 985 行命令,解释一下 cmake 的常用指令都在做什么,主要是为了阅读方便来操作,不是说“要学习 CMake 语法,自己书写 CMakeList.txt”。

  • 起手式:添加 cmake 最低版本限制、设置项目名称。(如果不执行这两个指令,使用 CMake 生成 Makefile 无法成功)

    cmake_minimum_required(VERSION 3.XX) (这个指令结构比较简单,括号内只有 VERSION 和一个版本号。

    project(gwtest) 支持指定语言、设置版本、添加描述,分别在括号内加入 LANGUAGES VERSION DESCRIPTION 几个关键字就好了。 也可以省略关键词,这时候这些参数的顺序必须按照 (版本) -> (语言) -> (描述) 的相对顺序

  • 将当前 CMakeList.txt 和子文件夹中的 CMakeList.txt 相连接

    add_subdirectory(FILE_NAME)

    这个指令大大提升了项目结构的可扩展性(增减性),不用再根目录下一个 CMakeList.txt handle 一切、乱七八糟难以调整了。

    我的理解是在通过通过 CMakeList.txt 生成 Makefile 时,编译器会将子文件夹的 CMakeList.txt 内容附录到当前的 CMakeList 对应的 add_subdirectory 处。如果加入子文件路径后子文件夹里面没有 CMakeList 会报错。

  • 变量设置

    set(variable_name value)

    value 一定是一个字符串,如果是没有空格的字符串,那么可以不用加引号,比如 set(output_file,a.exe)。

    下面是该项目涉及到的 set 命令中定义的变量名称。我觉得可能常用的是编译器的设置

    1. 编译器和构建配置

    CMAKE_CXX_STANDARD 20
    设置 C++ 编译标准为 C++20。
    CMAKE_CXX_STANDARD_REQUIRED ON
    强制要求编译器必须支持指定的 C++ 标准,若不支持则报错。

    CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=deprecated-copy"
    向 C++ 编译器标志添加 -Wno-error=deprecated-copy,将弃用拷贝操作的警告不视为错误。

    CMAKE_CXX_FLAGS_RELEASE "${...} -DNDEBUG -O2"
    在 Release 模式下添加预定义宏 NDEBUG(禁用断言)和优化选项 -O2

    CMAKE_BUILD_TYPE Release
    指定构建类型为 Release(启用优化,禁用调试符号)。

    CMAKE_POSITION_INDEPENDENT_CODE ON
    要求生成位置无关代码(PIC),通常用于构建共享库。


    2. 输出路径控制

    CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${...}/lib"
    静态库(.a/.lib)的输出目录设为二进制目录下的 lib 文件夹。

    CMAKE_RUNTIME_OUTPUT_DIRECTORY ${...}/bin
    可执行文件(如 .exe)的输出目录设为二进制目录下的 bin 文件夹。

    LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin
    共享库(.so/.dll)的输出目录设为构建目录下的 bin 文件夹(与可执行文件共用)。


    3. 链接器配置

    CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++"
    为可执行文件链接器添加静态链接 GCC 和 C++ 标准库的标志。

    CMAKE_SHARED_LINKER_FLAGS "-static-libgcc -static-libstdc++"
    为共享库链接器添加相同的静态链接标志。

    CMAKE_BUILD_RPATH "$ORIGIN"
    设置运行时库搜索路径为 $ORIGIN(Linux),使得程序运行时能加载同级目录下的共享库。


    4. 项目依赖和工具链

    THIRDPARTY_DIR ${PROJECT_SOURCE_DIR}/thirdparty
    定义第三方库的根目录为项目源码目录下的 thirdparty 文件夹。

    PROJECT_VERSION ${GOOGLETEST_VERSION}
    设置项目版本号为 GOOGLETEST_VERSION 的值(此处为 1.10.0)。

    gtest_force_shared_crt ON/OFF
    控制 Google Test 是否强制使用共享 C 运行时库(Windows 特有)。


    5. 代码生成和导出

    CMAKE_EXPORT_COMPILE_COMMANDS ON
    生成 compile_commands.json 文件,供 Clang 工具链(如 clangd)分析代码。

    cmake_package_name GTest
    定义 CMake 包的名称为 GTest,用于生成配置文件(如 GTestConfig.cmake)。

    version_file "${...}ConfigVersion.cmake"
    定义版本配置文件的路径,用于 CMake 包版本兼容性检查。


  • 添加可执行文件

    add_executable(output_file_name source_code)

    这里的输出文件名也是一个不加引号的字符串,source_code 可以是一个列表(一些以空格隔开的字符串),也可以是一个文件,但是需要保证这些文件里面只有一个 main 函数作为入口


添加可执行文件的过程有很多种,可以非常多样地指定编译选项,输出路径等。在本项目中,我发现可执行文件的属性都是在 add_executable 之后指定的,主要使用了 set_target_properties 、 target_link_libraries 、target_include_directories

  • 设置可执行文件的属性

    set_target_properties 是 CMake 中用于为已创建的多个目标(如可执行文件、静态库或动态库)设置多个属性的命令。它允许你通过一次调用配置目标的多种行为,覆盖构建、链接、安装等阶段的细节。

    set_target_properties(target1 target2 ...
    	PROPERTIES
    	property1 value1
    	property2 value2
    	...
    )
    

    以下是 set_target_properties 中常见的属性及其作用(前置知识,cpp动态库的后缀名是 .so):

    目录类属性 用途
    OUTPUT_NAME 指定目标生成的二进制文件名称(不包含后缀,如 libfoo 而非 libfoo.so)。
    INSTALL PATH 目标在安装阶段的最终存放路径(如 /usr/local/bin)。
    BUILD PATH 目标在构建阶段的临时文件的输出路径(如 build/bin)。
    ARCHIVE_OUTPUT_DIRECTORY 静态库(.a/.lib)的输出目录。
    LIBRARY_OUTPUT_DIRECTORY 动态库(.so/.dll)的输出目录。
    RUNTIME_OUTPUT_DIRECTORY 可执行文件(.exe)的输出目录。
    编译器版本属性 用途
    VERSION 动态库的版本号(如 1.2.3)。
    CXX_STANDARD 指定 C++ 标准(如 11, 14, 17)。
    C_STANDARD 指定 C 标准(如 90, 99, 11)。
    编译选项类属性 用途
    INTERFACE_INCLUDE_DIRECTORIES 声明库的头文件路径,供依赖此库的其他目标使用。
    COMPILE_FLAGS 添加编译选项(如 -Wall),但更推荐用 target_compile_options
    LINK_FLAGS 添加链接选项(如 -pthread),但推荐用 target_link_options
    COMPILE_DEFINITIONS 添加预处理器宏定义(如 -DDEBUG)。
    IMPORTED_CONFIGURATIONS 指定导入目标(如预编译库)支持的构建类型(如 Debug;Release)。

    这里设置了很多编译选项,还有一种编译选项设置是范围设置,对子树里面可执行文件编译过程加某个选项,可以使用 add_compile_options 命令,例如 add_compile_options(-O2 -Wall -fsanitize=address,undefined)

    另有一个命令是 add_compile_definition,这个就是加入宏定义了。

  • 向可执行文件添加库。

    有一个前置的操作是,进行库的生成:add_library([target_name] [target_type] [source_code_list])

    这里的库可以指定成动态库 STATIC,静态库 SHARED,插件库 MODULE(运行时加载),依赖(头文件,类的定义)传递 INTERFACE。里面有很多说道,但是我一概不掌握,可以参考下面 deepseek 的说法:

    在 CMake 中,target_include_directoriesPUBLICPRIVATEINTERFACE 关键字用于控制头文件包含路径的作用域,决定了这些路径如何传递给依赖当前目标的代码(例如其他库或可执行程序)。以下是它们的核心区别和用法:


    1. 作用域分类

    关键字 作用
    PRIVATE 路径仅用于当前目标的编译(内部实现使用),不会传递给依赖它的其他目标。
    PUBLIC 路径既用于当前目标的编译,同时也会传递给依赖它的其他目标(例如公开头文件)。
    INTERFACE 路径不用于当前目标的编译,但会传递给依赖它的其他目标(例如纯头文件库的包含路径)。

    2. 典型场景示例

    场景 1:静态库(Static Library)

    假设有一个静态库 mylib,目录结构如下:

    project/
    ├── include/           # 公共头文件(对外暴露)
    │   └── mylib.h
    ├── src/               # 私有实现
    │   ├── mylib.cpp
    │   └── internal.h    # 内部头文件
    

    CMakeLists.txt 配置:

    add_library(mylib STATIC src/mylib.cpp)
    
    # 公共头文件路径:对使用者可见(PUBLIC)
    target_include_directories(mylib
    	PUBLIC
    		$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    )
    
    # 私有头文件路径:仅内部使用(PRIVATE)
    target_include_directories(mylib
    	PRIVATE
    		${CMAKE_CURRENT_SOURCE_DIR}/src
    )
    

    效果
    • 当其他目标(如可执行文件)链接 mylib 时,会自动获得 include/ 路径(通过 PUBLIC 传递)。
    src/ 路径仅用于编译 mylib,不会污染其他目标。


    场景 2:动态库(Shared Library)

    动态库的作用域规则与静态库相同。例如:

    add_library(mylib SHARED src/mylib.cpp)
    
    # 公共头文件对外暴露
    target_include_directories(mylib
    	PUBLIC
    		$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    )
    

    场景 3:纯头文件库(Header-Only Library)

    对于仅包含头文件的库(无源文件),使用 INTERFACE

    add_library(header_only INTERFACE)
    
    # 头文件路径仅传递给使用者(不用于自身编译)
    target_include_directories(header_only
    	INTERFACE
    		${CMAKE_CURRENT_SOURCE_DIR}/include
    )
    

    3. 关键区别对比

    假设库 A 依赖库 B

    作用域 B 的包含路径对 A 是否可见? A 的包含路径对 B 的使用者是否可见?
    B 用 PRIVATE ❌ 不可见 ❌ 不可见
    B 用 PUBLIC ✅ 可见 ✅ 可见
    B 用 INTERFACE ✅ 可见 ❌ 不可见

    4. 最佳实践

    1. 公共头文件:使用 PUBLIC,确保使用者能正确找到头文件。
    2. 私有头文件:使用 PRIVATE,避免暴露实现细节。
    3. 头文件库:使用 INTERFACE,避免路径被自身编译使用。
    4. 避免全局作用域:不要滥用 include_directories(),优先用 target_include_directories

    5. **完整示例

    假设有以下依赖链:

    可执行程序 (app) → 静态库 (mylib) → 内部工具库 (utils)
    

    CMakeLists.txt 配置:

    # 内部工具库 utils(私有实现)
    add_library(utils STATIC src/utils.cpp)
    target_include_directories(utils PRIVATE src/utils)
    
    # 主库 mylib(依赖 utils)
    add_library(mylib STATIC src/mylib.cpp)
    target_include_directories(mylib
    	PUBLIC include     # 公共头文件
    	PRIVATE src/mylib  # 私有头文件
    )
    target_link_libraries(mylib PRIVATE utils)  # 仅内部依赖
    
    # 可执行程序 app(依赖 mylib)
    add_executable(app main.cpp)
    target_link_libraries(app PUBLIC mylib)     # 自动获得 mylib 的 PUBLIC 头文件路径
    

    结果
    app 自动获得 mylibinclude/ 路径。
    app 不会获得 utils 的私有头文件路径(因为 utilsPRIVATE 链接到 mylib)。


    通过合理使用 PUBLIC/PRIVATE,可以精准控制依赖传递,避免头文件路径污染和编译错误!

    add_library 的过程中也实现了对库的编译,接下来可以描述可执行文件都需要引用哪些文件。这些文件将在编译的第一步被直接粘贴到源代码中。

    target_include_directories(file_name 
    	[TYPE_NAME1]
    		lib_A 
    		lib_B
    		...
    	[TYPE_NAME2]
    		...
    	...
    )
    

    这个命令就设置了 file_name 对象的 include 地址。

  • 指明可执行文件所使用的库的链接情况

    和上面的情况类似。这里 TYPE_NAME 这个关键词的相关用法和上面 add_library 处给的 deepseek response 保持一致。

    target_link_libraries(file_name,
    	[TYPE_NAME1]
    		lib_A
    		lib_B
    	...
    )
    
  • install add_dependency 和 find_package

    来自 deepseek. 在 CMake 中,`install`、`find_package` 和 `add_dependencies` 是管理项目构建、依赖和安装的关键命令。以下是它们的详细用法和示例:

    1. install:安装目标或文件到指定路径

    作用:将构建生成的目标(如可执行文件、库)或普通文件(如头文件、配置文件)安装到指定目录,便于分发或供其他项目使用。

    基本语法:

    install(TARGETS <目标名> DESTINATION <路径>)
    install(FILES <文件列表> DESTINATION <路径>)
    install(DIRECTORY <目录路径> DESTINATION <路径>)
    

    常见用法:

    安装可执行文件

    install(TARGETS my_app DESTINATION bin)
    

    安装库和头文件

    install(TARGETS my_lib DESTINATION lib)
    install(FILES my_header.h DESTINATION include)
    

    安装整个目录(如资源文件):

    install(DIRECTORY resources/ DESTINATION share/my_project)
    

    高级用法:

    导出目标供其他项目使用

    install(TARGETS my_lib EXPORT MyLibTargets
    	DESTINATION lib
    	INCLUDES DESTINATION include
    )
    install(EXPORT MyLibTargets DESTINATION lib/cmake/MyLib)
    

    这会生成 MyLibTargets.cmake,其他项目可通过 find_package 导入。

    配置安装路径:

    使用 CMAKE_INSTALL_PREFIX 指定根目录:

    cmake -B build -DCMAKE_INSTALL_PREFIX=/usr/local
    cmake --build build --target install
    

    2. find_package:查找外部依赖

    作用:定位系统或用户提供的第三方库,自动设置包含路径、库路径等变量。

    基本语法:

    find_package(<包名> [版本] [REQUIRED] [COMPONENTS 组件...])
    

    查找模式:

    模块模式:查找 Find<包名>.cmake 文件(通常位于 CMAKE_MODULE_PATH)。
    配置模式:查找 <包名>Config.cmake<包名>-config.cmake(通常在 <前缀>/lib/cmake 中)。

    示例:

    find_package(OpenCV 4.0 REQUIRED COMPONENTS core videoio)
    include_directories(${OpenCV_INCLUDE_DIRS})
    target_link_libraries(my_app ${OpenCV_LIBRARIES})
    

    自定义查找路径:

    • 设置 CMAKE_PREFIX_PATHPackageName_DIR 变量:

    set(CMAKE_PREFIX_PATH "/path/to/opencv")
    find_package(OpenCV REQUIRED)
    

    常见变量:

    <包名>_FOUND:是否找到包。
    <包名>_INCLUDE_DIRS:头文件路径。
    <包名>_LIBRARIES:库文件路径。


    3. add_dependencies:显式添加目标依赖

    作用:确保某个目标在构建另一个目标之前先被构建。通常用于处理非自动推断的依赖关系(如自定义命令生成的文件)。

    基本语法:

    add_dependencies(<目标> <依赖项>)
    

    常见场景:

    依赖自定义目标

    add_custom_target(generate_files COMMAND ...)
    add_executable(my_app main.cpp)
    add_dependencies(my_app generate_files)  # 确保 generate_files 先构建
    

    跨项目依赖(如通过 add_subdirectory 引入的子项目):

    add_subdirectory(lib_project)
    add_executable(my_app main.cpp)
    add_dependencies(my_app lib_project_target)
    

    注意:

    target_link_libraries 的区别
    target_link_libraries 会自动添加库依赖,但 add_dependencies 用于非链接依赖(如确保生成文件存在)。


CMake 还有一个可变的因素就是构建配置(build configuration),主要有Debug,Release,RelWithDebInfo,MinSizeRel四种,考虑 debug release 两种,下面是 deepseek 的一些讲解:

CMake 中 release mode 和 debug mode 分别有什么区别

1. 优化级别(Optimization Level)

Release 模式
优化目标:最大化运行效率(速度/体积)。
编译器选项:通常启用高级优化(如 -O3/O2)。
副作用:代码可能难以调试(优化后的代码可能与源码不对应)。

Debug 模式
优化目标:保留调试信息,禁用优化。
编译器选项:通常关闭优化(-O0/Od)。
副作用:生成的可执行文件较大且运行较慢,但便于调试。


2. 调试信息(Debug Symbols)

Release 模式
默认行为:不生成调试符号(如 .pdb 文件)。
可配置性:可手动启用(但会增加文件体积)。

Debug 模式
默认行为:生成完整的调试符号(用于 GDB、LLDB 或 Visual Studio 调试器)。
重要性:允许断点、变量检查等调试操作。


3. 断言(Assertions)

Release 模式
默认行为:禁用 assert() 宏(通过 NDEBUG 宏定义)。
目的:避免性能损耗。

Debug 模式
默认行为:启用 assert() 宏(未定义 NDEBUG)。
目的:帮助开发者快速发现逻辑错误。


4. 其他编译器选项

Release 模式
• 可能启用链接时优化(LTO, -flto)。
• 可能删除未使用的代码和符号(-fdata-sections -ffunction-sections + --gc-sections)。

Debug 模式
• 启用运行时检查(如数组越界检查 /RTC1)。
• 禁用内联展开(保留完整的函数调用栈)。


5. CMake 中的配置

设置构建类型

在 CMake 中,通过 CMAKE_BUILD_TYPE 变量指定构建模式:

# 在命令行中指定
cmake -DCMAKE_BUILD_TYPE=Release ..

# 或在 CMakeLists.txt 中设置默认值
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Debug)
endif()

条件化配置

可通过生成器表达式(Generator Expressions)根据构建类型动态设置编译选项:

add_compile_definitions(
    "$<$<CONFIG:Debug>:DEBUG_MODE>"
    "$<$<CONFIG:Release>:RELEASE_MODE>"
)

6. 适用场景

模式 适用场景
Debug 开发阶段、调试崩溃问题、内存泄漏检查(如 Valgrind)。
Release 性能测试、最终发布版本、用户交付。

7. 性能与调试对比

特性 Debug 模式 Release 模式
运行速度 慢(无优化) 快(高度优化)
文件体积 大(含调试符号) 小(无冗余信息)
调试支持 完整(断点、变量查看) 困难(代码被优化)
安全性检查 启用(如越界检查) 禁用(牺牲检查换速度)

最佳实践

  1. 开发阶段用 Debug:确保可调试性。
  2. 发布前切 Release:测试性能并验证优化后的逻辑正确性。
  3. 避免混合配置:不要将 Debug 库与 Release 可执行文件混用(可能导致内存错误)。
还有什么别的 mode 吗

除了 DebugRelease,CMake 还支持其他预定义的构建模式(Build Types),开发者也可以自定义模式。以下是常见的模式及其用途:


一、CMake 内置的标准模式

1. RelWithDebInfo (Release with Debug Info)

特点
• 启用优化(类似 Release,默认 -O2/O2)。
保留调试符号(可调试,但文件体积较大)。
用途
• 性能接近 Release,同时保留调试信息用于生产环境的问题追踪。

2. MinSizeRel (Minimal Size Release)

特点
• 优化目标为最小化二进制体积(如 -Os/O1)。
• 不保留调试符号。
用途
• 适用于对磁盘/内存占用敏感的场景(如嵌入式设备)。

3. 其他标准模式

None:无默认优化或调试标志(需手动配置)。
Custom:用户完全自定义(依赖项目或工具链定义)。


二、常见自定义模式

开发者可通过设置 CMAKE_CXX_FLAGS_<MODE> 等变量扩展自定义模式,例如:

1. Profile (性能分析)

特点
• 启用优化(如 -O2)。
• 添加性能分析标志(如 -pg 生成 gprof 数据)。
用途
• 用于代码性能剖析(Profiling)。

2. Sanitizer (内存/线程检测)

特点
• 启用 AddressSanitizer (-fsanitize=address) 或 ThreadSanitizer (-fsanitize=thread)。
• 通常关闭优化以保留检测精度。
用途
• 检测内存泄漏、越界访问、竞态条件等。

3. Coverage (代码覆盖率)

特点
• 添加覆盖率检测标志(如 --coverage-fprofile-arcs -ftest-coverage)。
• 生成 .gcda/.gcno 文件供 lcovgcov 分析。
用途
• 统计单元测试的代码覆盖率。


三、模式对比表

模式 优化级别 调试符号 典型用途
Debug -O0 保留 开发调试
Release -O3 不保留 生产环境
RelWithDebInfo -O2 保留 生产环境问题追踪
MinSizeRel -Os 不保留 最小化体积
Profile -O2 保留 性能分析
Sanitizer -O0 保留 内存/线程安全检查
Coverage -O0 保留 测试覆盖率统计

四、如何使用这些模式

1. 命令行指定

cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
make

2. CMakeLists.txt 中设置默认值

if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Debug CACHE STRING "选择构建类型" FORCE)
endif()

3. 自定义模式配置

# 定义自定义模式 "Profile"
set(CMAKE_CXX_FLAGS_PROFILE "-O2 -pg")
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "-pg")

五、注意事项

  1. 模式兼容性:不同编译器支持的优化/调试标志可能不同(如 GCC 与 MSVC)。
  2. 依赖管理:确保依赖库的构建模式与主项目一致(避免 Debug/Release 混用导致的崩溃)。
  3. 跨平台:自定义模式需考虑不同操作系统(Linux/macOS/Windows)的编译差异。

通过合理选择或定义构建模式,可以更高效地适配开发、测试和生产环境的需求。


某项目中的部分代码源文件,如果出于各种原因不宜直接传递,可以将其编译为动态库,再进行传递。将 .cxx 文件中的具体实现编译成 .so,将 .hxx 文件中的定义加注释,正常传递,甚至不影响阅读。编译时直接对动态库进行链接即可。

下面是 deepseek 对 static library/shared library/head-only library 的一些解读。

在C++中,静态库、动态库和仅头文件库是三种常见的代码复用方式,它们各有特点和应用场景。以下是它们的详细对比:


1. 静态库(Static Library)

定义
静态库在编译时(链接阶段)直接嵌入到可执行文件中。代码会被完整复制到最终的可执行文件内。
文件格式:Windows 为 .lib(与动态库的导入库同名,需注意区分),Linux 为 .a
示例工具ar(Linux)、lib.exe(Windows)。

优点
无运行时依赖:程序独立运行,无需额外库文件。
性能优化:编译时链接允许链接器优化代码。
版本兼容性:避免动态库版本冲突问题。

缺点
体积大:每个使用静态库的程序都包含库代码的副本。
更新困难:库更新需重新编译整个程序。

适用场景
需要独立部署、避免依赖或对性能敏感的场景(如嵌入式系统)。


2. 动态库(Dynamic Library / Shared Library)

定义
动态库在程序运行时加载,代码不会被复制到可执行文件中,而是由操作系统动态链接。
文件格式:Windows 为 .dll,Linux 为 .so
加载方式:隐式加载(启动时自动加载)或显式加载(通过 LoadLibrary/dlopen 手动加载)。

优点
节省资源:多个程序共享同一物理文件,减少内存和磁盘占用。
更新灵活:替换动态库文件即可更新功能(需保持 ABI 兼容)。

缺点
依赖管理:程序运行时需确保动态库存在且版本兼容。
性能开销:首次加载或符号解析可能带来轻微延迟。

适用场景
系统级共享库(如 C 标准库 libc.so)或插件化架构。


3. 仅头文件库(Header-only Library)

定义
所有实现代码均位于头文件(.h.hpp)中,无需编译,直接通过 #include 使用。
示例库:Eigen(线性代数)、spdlog(日志库)、Boost.Asio(部分功能)。

优点
跨平台:无编译依赖,使用简单。
模板友好:适合需要编译时实例化的模板代码。

缺点
编译时间长:头文件内容在每次编译时都会处理。
代码膨胀:模板可能导致生成的多份实例增大二进制体积。

适用场景
轻量级库、模板库或需要快速原型设计的项目。


三者的关键区别

特性 静态库 动态库 仅头文件库
整合时机 编译时(链接阶段) 运行时 编译时(预处理阶段)
代码复制 复制到可执行文件 不复制,动态加载 复制到每个调用处
运行时依赖 必须存在对应库文件
二进制体积 较大(含库代码) 较小 可能较大(模板实例化)
更新维护 需重新编译程序 替换库文件(ABI 兼容) 修改头文件后重新编译
适用场景 独立部署、无依赖 资源共享、插件化 模板库、快速开发

总结

静态库:适合追求独立性和性能,避免运行时依赖的场景。
动态库:适合资源共享、频繁更新或需要插件化架构的项目。
仅头文件库:适合轻量级或模板代码,简化跨平台部署。

根据项目需求(如部署环境、维护成本、性能要求)合理选择库类型,可以显著提升开发效率和程序质量。

理论上某项目还使用了 GoogleTest,GTEST 里面涉及了大量复杂命令,但是我先不掌握了。

posted @ 2025-03-20 14:51  没学完四大礼包不改名  阅读(126)  评论(1)    收藏  举报