> 技术文档 > CMake进阶: 检查函数/符号存在性、检查类型/关键字/表达式有效性和检查编译器特性

CMake进阶: 检查函数/符号存在性、检查类型/关键字/表达式有效性和检查编译器特性

目录

1.前言

2. 检查函数/符号存在性

2.1.CheckLibraryExists(库中函数)

2.1.1.简介

2.1.2.使用前提

2.1.3.进阶用法

2.1.4.常见问题与解决

2.2.check_c_symbol_exists(C 符号)

2.2.1.简介

2.2.2.使用前提

2.2.3.核心工作原理

2.2.4.与 CheckLibraryExists 的区别

2.2.5.进阶用法

2.2.6.常见问题与解决

2.3.CheckFunctionExists(C 函数,不指定库)

2.3.1.简介

2.3.2.使用前提

2.3.3.核心工作原理

2.3.4.与其他工具的区别

2.3.5.局限性与注意事项

2.3.6.总结

3.检查类型/关键字/表达式有效性

3.1.CheckTypeSize(类型大小)

3.1.1.简介

3.1.2.使用前提

3.1.3.核心工作原理

3.1.4.进阶用法

3.2.CheckCXXTypeExists(C++ 类型存在性)

3.2.1.简介

3.2.2.使用前提

3.2.3.核心工作原理

3.2.4.注意事项

3.3.CheckCXXKeywordExists(C++ 关键字)

3.3.1.简介

3.3.2.使用前提

3.3.3.注意事项

3.3.4.常见使用场景

3.4.CheckCSourceCompiles

3.4.1.简介

3.4.2.使用前提

3.4.3.基本用法示例

3.4.4.注意事项

3.5.CheckCXXSourceCompiles 

4.检查编译器特性

4.1.CheckCXXCompilerFlag(C++ 编译器标志)

4.1.1.简介

4.1.2.使用前提

4.1.3.进阶用法

4.1.4.注意事项

4.2.CheckCCompilerFlag(C编译器标志)

4.3.CheckLinkerFlag(链接器标志)

4.3.1.简介

4.3.2.使用前提

4.3.3.基本用法示例

4.3.4.注意事项

5.总结

相关链接


1.前言

 之前讲了在CMake中检查头文件存在性:

CMake进阶:检查头文件存在性(check_include_file 和 check_include_fileCXX)-CSDN博客

其实CMake中还有很多检测其它特性的宏,下面就来一一介绍一下。

2. 检查函数/符号存在性

2.1.CheckLibraryExists(库中函数)

2.1.1.简介

        CheckLibraryExists 是 CMake 提供的一个模块,用于检查指定的库中是否存在某个函数(或符号),并根据检查结果设置相应的变量。它主要用于跨平台项目中验证库的功能完整性,确保依赖库包含所需的函数接口。

        基本语法:

check_library_exists(   )
  • :要检查的库名称(如 m 表示数学库,pthread 表示线程库)。
  • :要检查的函数名(如 sqrtpthread_create)。
  • :包含该函数声明的头文件路径(可指定多个,用分号分隔)。
  • :输出变量名,若找到函数则设为 1TRUE),否则为 0FALSE)。

2.1.2.使用前提

需先通过 include 命令加载 CheckLibraryExists 模块:

include(CheckLibraryExists) # 加载模块

基本用法示例:

# 加载模块include(CheckLibraryExists)# 检查数学库(m)中是否存在 sqrt 函数,依赖头文件 math.hcheck_library_exists(m sqrt \"math.h\" HAVE_SQRT_IN_M_LIB)# 检查 pthread 库中是否存在 pthread_create 函数,依赖头文件 pthread.hcheck_library_exists(pthread pthread_create \"pthread.h\" HAVE_PTHREAD_CREATE)# 根据检查结果条件处理if(HAVE_SQRT_IN_M_LIB) message(STATUS \"数学库中找到 sqrt 函数\") target_link_libraries(myapp PRIVATE m) # 链接数学库else() message(WARNING \"数学库中未找到 sqrt 函数\")endif()if(NOT HAVE_PTHREAD_CREATE) message(FATAL_ERROR \"pthread 库中未找到 pthread_create 函数,无法编译多线程功能\")endif()

2.1.3.进阶用法

1.检查自定义库或非标准路径的库

若库位于非系统默认路径(如自定义编译的库),需先通过 LINK_DIRECTORIES 或 CMAKE_LIBRARY_PATH 指定库路径,再进行检查:

# 指定库所在路径(如 ./libs 目录)link_directories(${CMAKE_SOURCE_DIR}/libs)# 检查自定义库 mylib 中是否存在 my_function 函数check_library_exists(mylib my_function \"myheader.h\" HAVE_MY_FUNCTION)

2.结合头文件检查

通常与 check_include_file 配合使用,先确认头文件存在,再检查库中是否有对应的函数:

include(CheckIncludeFile)include(CheckLibraryExists)# 先检查头文件是否存在check_include_file(\"boost/system.hpp\" HAVE_BOOST_SYSTEM_H LANGUAGE CXX)if(HAVE_BOOST_SYSTEM_H) # 再检查 boost_system 库中是否存在 boost::system::error_code 相关符号 # (注:对于 C++ 函数,需确保名称修饰正确,可能需要额外处理) check_library_exists(boost_system \"_ZN5boost6system10error_codeC1Ev\" \"boost/system.hpp\" HAVE_BOOST_ERROR_CODE)endif()

3.处理 C++ 函数(名称修饰问题)

C++ 函数会被编译器进行名称修饰(name mangling),直接使用函数名可能无法匹配。此时需:

  1. 先通过 nm 或 objdump 工具查看库中实际的符号名(如 _ZN5boost6system...);
  2. 在 check_library_exists 中使用修饰后的符号名。

示例(检查 Boost 库中的函数):

# 检查 boost_system 库中 boost::system::error_code 的构造函数(修饰后符号)check_library_exists( boost_system \"_ZN5boost6system10error_codeC1Ev\" # 修饰后的符号名 \"boost/system.hpp\" HAVE_BOOST_ERROR_CODE)

2.1.4.常见问题与解决

1.找不到库(错误提示:Cannot find library

  • 原因:库不在系统默认路径,且未通过 link_directories 或 CMAKE_LIBRARY_PATH 指定。
  • 解决
set(CMAKE_LIBRARY_PATH \"${CMAKE_LIBRARY_PATH};/path/to/library/dir\") # 添加库路径check_library_exists(mylib my_func \"myheader.h\" HAVE_MY_FUNC)

2.函数存在但检查失败(C++ 名称修饰)

  • 原因:C++ 函数名被修饰,与传入的  不匹配。
  • 解决
    使用 nm libxxx.so 查看实际符号名,例如:
nm libboost_system.so | grep error_code # 找到修饰后的符号

然后在 check_library_exists 中使用该符号名。

3.跨平台差异(如 Windows 静态库)

  • 原因:Windows 库可能需要特定前缀(如 lib)或后缀(如 .lib)。
  • 解决:结合 CMAKE_FIND_LIBRARY_PREFIXES 和 CMAKE_FIND_LIBRARY_SUFFIXES 适配:
if(WIN32) set(CMAKE_FIND_LIBRARY_PREFIXES \"lib\") # Windows 静态库可能带 lib 前缀endif()check_library_exists(mylib my_func \"myheader.h\" HAVE_MY_FUNC)

2.2.check_c_symbol_exists(C 符号)

2.2.1.简介

        check_c_symbol_exists 是 CMake 提供的一个宏(macro),用于检查 C 语言符号(如函数、变量、宏等)是否存在于指定的头文件中,并根据检查结果设置相应的变量。它主要用于跨平台 C 项目中验证系统或库的 C 语言接口是否可用,无需链接阶段,仅通过编译检查即可完成。

        基本语法:

check_c_symbol_exists(   [FLAGS ])
  • :要检查的 C 符号(函数名、变量名等,如 printfO_CREAT)。
  • :包含该符号声明的头文件(多个头文件用分号分隔,如 \"stdio.h;stdlib.h\")。
  • :输出变量名,若符号存在则设为 1TRUE),否则为 0FALSE)。
  • FLAGS (可选):传递给 C 编译器的额外参数(如 -I 指定包含路径、-D 定义宏)。

2.2.2.使用前提

需先加载 CheckCSymbolExists 模块:

include(CheckCSymbolExists) # 加载模块

基本用法示例:

# 加载模块include(CheckCSymbolExists)# 检查标准库中的 printf 函数(需要包含 stdio.h)check_c_symbol_exists(\"printf\" \"stdio.h\" HAVE_PRINTF)# 检查系统头文件中的 O_CREAT 宏(用于文件创建,需要 fcntl.h)check_c_symbol_exists(\"O_CREAT\" \"fcntl.h\" HAVE_O_CREAT)# 检查自定义库中的 my_c_func 函数(需要包含 mylib.h)check_c_symbol_exists( \"my_c_func\" \"mylib.h\" HAVE_MY_C_FUNC FLAGS \"-I${CMAKE_SOURCE_DIR}/include\" # 非标准路径的头文件)# 根据检查结果处理if(HAVE_PRINTF) message(STATUS \"找到 printf 函数,可使用标准输出\")else() message(WARNING \"未找到 printf(可能编译器不支持标准 C 库)\")endif()if(NOT HAVE_O_CREAT) message(FATAL_ERROR \"未找到 O_CREAT 宏,无法编译文件操作功能\")endif()

2.2.3.核心工作原理

check_c_symbol_exists 通过以下步骤验证符号是否存在:

1.生成临时 C 测试代码

自动生成包含指定头文件和符号引用的 C 代码,例如检查 printf 时:

#include  // 用户指定的头文件int main(void) { (void)printf; // 引用目标符号(仅编译检查,不执行) return 0;}

2.编译测试代码

使用 C 编译器(如 gcccl.exe)编译上述代码,若编译成功(无 “未声明的标识符” 错误),说明符号存在;若编译失败,则符号不存在。

3.设置结果变量

根据编译结果,将  设为 1(成功)或 0(失败),并缓存结果到 CMakeCache.txt

2.2.4.与 CheckLibraryExists 的区别

特性 check_c_symbol_exists CheckLibraryExists 检查阶段 仅编译阶段(无需链接) 需链接阶段(验证库中符号) 适用符号类型 C 函数、宏、变量(声明即可) 库中的函数(需实际定义) 依赖库 不依赖具体库(仅需头文件) 必须指定库(如 mpthread) C++ 符号支持 不支持(C++ 名称修饰问题) 支持(需手动处理修饰符号)

2.2.5.进阶用法

1.检查依赖多个头文件的符号

若符号的声明依赖多个头文件,用分号分隔传入:

# 检查 pthread_t 类型(需要 pthread.h)和 pthread_create 函数check_c_symbol_exists( \"pthread_create\" \"pthread.h;stdio.h\" # 多个头文件 HAVE_PTHREAD_CREATE)

2.检查需要特定编译选项的符号

部分符号依赖编译器宏定义(如 _GNU_SOURCE 启用 GNU 扩展),可通过 FLAGS 指定:

# 检查 GNU 扩展中的 asprintf 函数(需要定义 _GNU_SOURCE)check_c_symbol_exists( \"asprintf\" \"stdio.h\" HAVE_ASPRINTF FLAGS \"-D_GNU_SOURCE\" # 启用 GNU 扩展)

2.2.6.常见问题与解决

 1.符号存在但检查失败

  • 原因
    • 未包含符号所在的头文件(如检查 pthread_create 却未传入 pthread.h);
    • 符号依赖特定宏定义(如 GNU 扩展符号需要 -D_GNU_SOURCE);
    • 头文件路径错误,未通过 FLAGS 添加 -I 选项。
  • 解决
# 正确示例:包含必要头文件并添加宏定义check_c_symbol_exists( \"getline\" # GNU 扩展函数,需要 _GNU_SOURCE \"stdio.h\" HAVE_GETLINE FLAGS \"-D_GNU_SOURCE\")

2.混淆 C 和 C++ 符号

  • 原因check_c_symbol_exists 仅支持 C 符号,检查 C++ 符号(如 std::string)会失败。
  • 解决:检查 C++ 符号需用 check_cxx_symbol_exists

2.3.CheckFunctionExists(C 函数,不指定库)

2.3.1.简介

        CheckFunctionExists 是 CMake 提供的一个模块,用于检查当前编译环境中是否存在指定的 C 语言函数,并根据检查结果设置相应的变量。它通过链接阶段验证函数是否可被找到(无需指定具体库,依赖默认链接的系统库),适用于检查系统默认库中是否包含目标函数。

        基本语法:

check_function_exists( )
  • :要检查的 C 函数名(如 mallocgetpid)。
  • :输出变量名,若函数存在则设为 1TRUE),否则为 0FALSE)。

2.3.2.使用前提

需先加载 CheckFunctionExists 模块:

include(CheckFunctionExists) # 加载模块

基本用法示例:

# 加载模块include(CheckFunctionExists)# 检查标准库中是否存在 malloc 函数check_function_exists(\"malloc\" HAVE_MALLOC)# 检查系统调用 getpid 是否存在check_function_exists(\"getpid\" HAVE_GETPID)# 根据检查结果处理if(HAVE_MALLOC) message(STATUS \"找到 malloc 函数,可使用动态内存分配\")else() message(WARNING \"未找到 malloc 函数,需使用替代实现\")endif()if(NOT HAVE_GETPID) message(FATAL_ERROR \"未找到 getpid 函数,无法获取进程 ID\")endif()

2.3.3.核心工作原理

check_c_symbol_exists差不多,就不在这里赘述了。

2.3.4.与其他工具的区别

工具 核心差异点 适用场景 CheckFunctionExists 不指定库,依赖默认链接的系统库(如 libc) 检查系统默认库中的函数(如 mallocCheckLibraryExists 需指定具体库(如 mpthread) 检查特定库中的函数(如 sqrt 在 m 库中) check_c_symbol_exists 仅编译阶段检查(不链接),依赖头文件声明 检查头文件中是否声明了函数

2.3.5.局限性与注意事项

1.仅支持 C 函数

不支持 C++ 函数(因名称修饰问题),也不支持需要特定库链接的函数(如 pthread_create 需链接 pthread 库,此时应使用 CheckLibraryExists)。

2.依赖默认链接库

只能检查默认链接到项目中的库(如 libc)中的函数。若函数位于非默认库(如数学库 libm 中的 sqrt),检查会失败:

# 错误示例:sqrt 位于 libm 库,默认不链接,检查会失败check_function_exists(\"sqrt\" HAVE_SQRT) # 结果为 FALSE# 正确做法:用 CheckLibraryExists 指定库include(CheckLibraryExists)check_library_exists(m sqrt \"math.h\" HAVE_SQRT) # 正确

3.跨平台差异

不同系统的默认库函数可能不同(如 Windows 没有 fork,Linux 没有 CreateProcess),需结合平台判断:

if(UNIX) check_function_exists(\"fork\" HAVE_FORK)elseif(WIN32) check_function_exists(\"CreateProcessA\" HAVE_CREATEPROCESS)endif()

2.3.6.总结

CheckFunctionExists 适用于检查系统默认库(如 libc)中是否存在 C 函数,无需手动指定库路径,使用简单但场景有限。对于以下情况,需选择其他工具:

  • 函数位于非默认库中 → 使用 CheckLibraryExists
  • 仅需检查函数声明是否存在(不关心定义) → 使用 check_c_symbol_exists
  • 检查 C++ 函数 → 使用 check_cxx_symbol_exists

在跨平台 C 项目中,它常被用于验证基础系统调用(如 getpidmalloc)是否可用,确保代码兼容性。

3.检查类型/关键字/表达式有效性

3.1.CheckTypeSize(类型大小)

3.1.1.简介

        CheckTypeSize 是 CMake 提供的一个模块,用于检查指定数据类型(如 intlong 或自定义类型)在当前编译环境中的大小(以字节为单位),并将结果存储在变量中。这对于跨平台开发尤为重要,因为不同架构(如 32 位 / 64 位)或编译器可能对同一数据类型分配不同的内存空间。

        基本语法:

check_type_size(  [BUILTIN_TYPES_ONLY] [LANGUAGE ] [FLAGS ])
  • :要检查的类型名称(如 intsize_tmy_custom_type)。
  • :输出变量名,存储类型的大小(以字节为单位);若类型不存在,变量值为 0
  • BUILTIN_TYPES_ONLY(可选):仅检查编译器内置类型(如 int),不检查自定义类型。
  • LANGUAGE (可选):指定检查使用的语言(C 或 CXX,默认 C)。
  • FLAGS (可选):传递给编译器的额外参数(如 -std=c++17-I 指定头文件路径)。

3.1.2.使用前提

需先加载 CheckTypeSize 模块:

include(CheckTypeSize) # 加载模块

基本用法示例:

# 加载模块include(CheckTypeSize)# 检查基本内置类型(C 语言环境)check_type_size(\"int\" SIZEOF_INT)check_type_size(\"long\" SIZEOF_LONG)check_type_size(\"void*\" SIZEOF_VOID_P) # 指针类型的大小(判断 32/64 位系统)# 检查 C 标准库类型(需包含头文件,通过 FLAGS 指定)check_type_size( \"size_t\" SIZEOF_SIZE_T FLAGS \"-include stddef.h\" # 包含 stddef.h 以识别 size_t)# 检查 C++ 类型(需指定 LANGUAGE CXX)check_type_size( \"std::string\" SIZEOF_STD_STRING LANGUAGE CXX FLAGS \"-include string\" # 包含 string 头文件)# 检查结果处理if(SIZEOF_VOID_P EQUAL 8) message(STATUS \"64 位系统(指针大小为 8 字节)\")elseif(SIZEOF_VOID_P EQUAL 4) message(STATUS \"32 位系统(指针大小为 4 字节)\")endif()if(SIZEOF_STD_STRING GREATER 0) message(STATUS \"std::string 大小为 ${SIZEOF_STD_STRING} 字节\")else() message(WARNING \"未找到 std::string 类型\")endif()

3.1.3.核心工作原理

1.生成临时测试代码

自动生成包含目标类型的 C/C++ 代码,通过 sizeof 运算符获取大小,并将结果输出到宏定义中。例如检查 int 时:

#include int main() { return sizeof(int); // 编译时计算类型大小}

2.编译并运行测试程序

编译测试代码生成可执行文件,运行该程序后,其返回值即为类型的大小(字节数)。

3.解析结果并设置变量

CMake 捕获程序返回值,将其存储到  中(如 SIZEOF_INT=4);若类型不存在或编译失败,变量值为 0

3.1.4.进阶用法

1. 检查自定义类型

若要检查项目中的自定义类型(如 struct MyType),需通过 FLAGS 指定头文件路径和包含语句:

# 检查自定义结构体(位于 ./include/mytype.h)check_type_size( \"MyType\" SIZEOF_MY_TYPE FLAGS \"-I${CMAKE_SOURCE_DIR}/include -include mytype.h\" # 包含自定义头文件)if(SIZEOF_MY_TYPE EQUAL 16) message(STATUS \"MyType 大小符合预期(16 字节)\")else() message(WARNING \"MyType 大小异常(实际 ${SIZEOF_MY_TYPE} 字节)\")endif()

2.结合条件编译定义宏

将类型大小通过宏定义传递给源码,实现跨平台兼容:

# CMakeLists.txt 中check_type_size(\"long long\" SIZEOF_LONG_LONG)if(SIZEOF_LONG_LONG EQUAL 8) target_compile_definitions(myapp PRIVATE LONG_LONG_64BIT)endif()
// 源码中(myapp.c)#ifdef LONG_LONG_64BIT typedef long long int64_t; // 64 位系统使用 long long#else typedef long int64_t; // 32 位系统兼容处理#endif

3. 类型依赖特定编译标准

某些类型(如 C++11 的 std::array)依赖特定语言标准,未指定会导致检查失败。需要通过 FLAGS 指定编译标准:

check_type_size( \"std::array\" SIZEOF_STD_ARRAY LANGUAGE CXX FLAGS \"-std=c++11 -include array\")

3.2.CheckCXXTypeExists(C++ 类型存在性)

3.2.1.简介

        CheckCXXTypeExists 是 CMake 提供的一个模块,专门用于检查 C++ 中是否存在指定的类型(如标准库类型、自定义类 / 结构体等),并根据检查结果设置相应的变量。它通过编译一段包含目标类型的极简代码,验证该类型是否被编译器识别,适用于跨平台 C++ 项目中确认类型兼容性。

        基本语法:

check_cxx_type_exists(   [FLAGS ])
  • :要检查的 C++ 类型(如 std::vectormy::custom_type)。
  • :包含该类型声明的头文件(多个头文件用分号分隔,如 \"vector;string\")。
  • :输出变量名,若类型存在则设为 1TRUE),否则为 0FALSE)。
  • FLAGS (可选):传递给 C++ 编译器的额外参数(如 -std=c++17-I/path/to/include)。

3.2.2.使用前提

需先加载 CheckCXXTypeExists 模块:

include(CheckCXXTypeExists) # 加载模块

基本用法示例:

# 加载模块include(CheckCXXTypeExists)# 检查标准库类型(如 std::vector,需要包含 vector 头文件)check_cxx_type_exists(\"std::vector\" \"vector\" HAVE_STD_VECTOR)# 检查 C++11 引入的 std::thread(需要包含 thread 头文件和 C++11 标准)check_cxx_type_exists( \"std::thread\" \"thread\" HAVE_STD_THREAD FLAGS \"-std=c++11\" # 指定 C++11 标准)# 检查自定义命名空间中的类型(my::custom_type,需要包含 mytype.h)check_cxx_type_exists( \"my::custom_type\" \"mytype.h\" HAVE_MY_CUSTOM_TYPE FLAGS \"-I${CMAKE_SOURCE_DIR}/include\" # 自定义头文件路径)# 根据检查结果处理if(HAVE_STD_VECTOR) message(STATUS \"找到 std::vector,可使用动态数组功能\")else() message(WARNING \"未找到 std::vector(可能编译器不支持 C++ 标准库)\")endif()if(HAVE_STD_THREAD) message(STATUS \"支持 std::thread,可启用多线程功能\")else() message(STATUS \"不支持 std::thread,禁用多线程功能\")endif()

3.2.3.核心工作原理

1.生成临时 C++ 测试代码

自动生成包含指定头文件和类型引用的代码,例如检查 std::vector 时:

#include  // 用户指定的头文件int main() { (void)static_cast<std::vector*>(nullptr); // 引用目标类型(无实际执行) return 0;}

2.编译测试代码

使用 C++ 编译器编译上述代码,若编译成功(编译器能识别该类型),说明类型存在;若编译失败(如 “未声明的标识符”),则类型不存在。

3.设置结果变量

根据编译结果,将  设为 1(成功)或 0(失败),并缓存结果到 CMakeCache.txt

3.2.4.注意事项

1.类型需完整声明

目标类型必须在指定的头文件中完整声明(如 classstruct 或 typedef 定义),否则编译会失败。

2.依赖 C++ 标准

部分类型(如 std::optional 需 C++17)依赖特定 C++ 标准,需通过 FLAGS 指定(如 -std=c++17):

check_cxx_type_exists( \"std::optional\" \"optional\" HAVE_STD_OPTIONAL FLAGS \"-std=c++17\")

3.命名空间和模板类型

检查带命名空间或模板的类型时,需完整写出类型名(如 std::map),并确保头文件包含正确:

check_cxx_type_exists( \"std::map\" \"map;string\" # 需包含 map 和 string 头文件 HAVE_STD_MAP_STRING_INT)

3.3.CheckCXXKeywordExists(C++ 关键字)

3.3.1.简介

        CheckCXXKeywordExists 是 CMake 提供的一个模块,专门用于检查 C++ 编译器是否支持指定的 C++ 关键字(如 constexprdecltypeconcepts 等),并根据检查结果设置相应的变量。它通过编译一段包含目标关键字的极简代码,验证编译器对该关键字的支持程度,是跨平台 C++ 项目中处理语言特性兼容性的重要工具。

        基本语法:

check_cxx_keyword_exists(  [FLAGS ])
  • :要检查的 C++ 关键字(如 constexprrequiresnullptr)。
  • :输出变量名,若编译器支持该关键字则设为 1TRUE),否则为 0FALSE)。
  • FLAGS (可选):传递给 C++ 编译器的额外参数(如 -std=c++20 指定语言标准)。

3.3.2.使用前提

需先加载 CheckCXXKeywordExists 模块:

include(CheckCXXKeywordExists) # 加载模块

基本用法示例:

# 加载模块include(CheckCXXKeywordExists)# 检查 C++11 关键字 nullptrcheck_cxx_keyword_exists(\"nullptr\" HAVE_NULLPTR)# 检查 C++11 关键字 constexpr(需指定 C++11 及以上标准)check_cxx_keyword_exists( \"constexpr\" HAVE_CONSTEXPR FLAGS \"-std=c++11\" # 明确指定 C++ 标准)# 检查 C++20 关键字 requires(用于概念约束)check_cxx_keyword_exists( \"requires\" HAVE_REQUIRES_KEYWORD FLAGS \"-std=c++20\" # requires 是 C++20 引入的)# 根据检查结果处理if(HAVE_NULLPTR) message(STATUS \"编译器支持 nullptr 关键字\")else() message(WARNING \"编译器不支持 nullptr,需使用 NULL 替代\")endif()if(HAVE_REQUIRES_KEYWORD) message(STATUS \"支持 C++20 requires 关键字,可使用概念特性\") target_compile_features(myapp PRIVATE cxx_std_20)else() message(STATUS \"不支持 requires 关键字,禁用概念相关代码\")endif()

3.3.3.注意事项

1.依赖 C++ 标准版本

许多关键字属于特定 C++ 标准(如 constexpr 始于 C++11,requires 始于 C++20),需通过 FLAGS 指定对应的标准,否则检查可能误判:

# 错误示例:未指定 C++ 标准,可能导致 constexpr 检查失败check_cxx_keyword_exists(\"constexpr\" HAVE_CONSTEXPR) # 可能返回 FALSE# 正确示例:指定 C++11 及以上标准check_cxx_keyword_exists( \"constexpr\" HAVE_CONSTEXPR FLAGS \"-std=c++11\")

2.区分关键字与标识符

确保检查的是 C++ 标准定义的关键字,而非用户自定义标识符。例如 override 是 C++11 关键字,而 final 也是 C++11 引入的特殊标识符(严格来说是 “上下文相关关键字”),CheckCXXKeywordExists 也可检查这类特殊标识符。

3.跨编译器差异

不同编译器对关键字的支持可能不同(如某些编译器在早期版本中对 constexpr 的支持不完整),需结合编译器类型判断:

check_cxx_keyword_exists(\"constexpr\" HAVE_CONSTEXPR FLAGS \"-std=c++11\")if(HAVE_CONSTEXPR AND CMAKE_CXX_COMPILER_ID MATCHES \"GNU\" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6) message(STATUS \"GCC 4.7+ 支持 constexpr\")endif()

3.3.4.常见使用场景

1.条件启用语言特性

根据关键字支持情况,在源码中启用或禁用对应的 C++ 特性:

# CMakeLists.txt 中check_cxx_keyword_exists(\"constexpr\" HAVE_CONSTEXPR FLAGS \"-std=c++11\")if(HAVE_CONSTEXPR) target_compile_definitions(myapp PRIVATE SUPPORT_CONSTEXPR)endif()
// 源码中(feature.cpp)#ifdef SUPPORT_CONSTEXPR constexpr int max_size = 1024; // 使用 constexpr#else const int max_size = 1024; // 降级为 const#endif

2.验证编译器兼容性

在项目初始化阶段检查关键关键字,确保编译器满足最低版本要求:

# 检查 C++17 关键字 if constexprcheck_cxx_keyword_exists(\"if constexpr\" HAVE_IF_CONSTEXPR FLAGS \"-std=c++17\")if(NOT HAVE_IF_CONSTEXPR) message(FATAL_ERROR \"编译器不支持 C++17 if constexpr,需升级编译器\")endif()

3.4.CheckCSourceCompiles

3.4.1.简介

        CheckCSourceCompiles 是 CMake 提供的一个模块,用于检查一段 C 语言代码能否被当前编译器成功编译(包括预处理、编译和链接阶段),并根据检查结果设置相应的变量。它主要用于跨平台 C 项目中验证编译器特性、代码片段的兼容性或特定库功能的可用性。

        基本语法:

check_c_source_compiles(  [FLAGS ])
  • :要检查的 C 语言代码片段(必须是完整可编译的程序,包含 main 函数)。
  • :输出变量名,若代码编译成功则设为 1TRUE),否则为 0FALSE)。
  • FLAGS (可选):传递给 C 编译器的额外参数(如 -std=c99 指定 C 标准、-I 包含路径)。

3.4.2.使用前提

需先加载 CheckCSourceCompiles 模块:

include(CheckCSourceCompiles) # 加载模块

3.4.3.基本用法示例

1.检查编译器对 C99 特性的支持

# 加载模块include(CheckCSourceCompiles)# 检查 C99 中的变长数组(VLA)是否支持check_c_source_compiles(\" #include  int main() { int n = 10; int arr[n]; // C99 变长数组特性 arr[0] = 0; return 0; }\" SUPPORTS_C99_VLA FLAGS \"-std=c99\" # 指定 C99 标准)# 检查结果处理if(SUPPORTS_C99_VLA) message(STATUS \"编译器支持 C99 变长数组\")else() message(WARNING \"编译器不支持 C99 变长数组,需使用动态分配替代\")endif()

2.检查系统调用或库函数的可用性

# 检查是否支持 pipe2 系统调用(需要 _GNU_SOURCE 宏)check_c_source_compiles(\" #define _GNU_SOURCE // 启用 GNU 扩展 #include  int main() { int fds[2]; int ret = pipe2(fds, O_CLOEXEC); // 目标系统调用 return ret; }\" HAVE_PIPE2 FLAGS \"-std=c11\")if(HAVE_PIPE2) message(STATUS \"支持 pipe2 系统调用,可使用 O_CLOEXEC 标志\")else() message(STATUS \"不支持 pipe2,使用 pipe + fcntl 替代\")endif()

3.检查自定义宏或类型的行为

# 检查自定义宏 MY_MACRO 的展开是否符合预期check_c_source_compiles(\" #include \\\"myheader.h\\\" // 包含自定义宏定义 int main() { int x = MY_MACRO(5); // 使用自定义宏 if (x != 10) return 1; // 验证宏展开结果 return 0; }\" MY_MACRO_WORKS FLAGS \"-I${CMAKE_SOURCE_DIR}/include\" # 自定义头文件路径)if(MY_MACRO_WORKS) message(STATUS \"自定义宏 MY_MACRO 行为符合预期\")else() message(FATAL_ERROR \"MY_MACRO 定义错误,无法继续编译\")endif()

3.4.4.注意事项

1.代码必须完整

传入的  必须是可独立编译的 C 程序,至少包含 main 函数。例如,仅写 int x = 5; 会编译失败,需包裹在 main 中。

2.处理依赖和宏定义

若代码依赖特定头文件、宏定义或库,需显式包含或指定:

# 示例:依赖 math 库的代码check_c_source_compiles(\" #include  int main() { double x = sqrt(2.0); // 依赖 libm 库 return 0; }\" SQRT_WORKSFLAGS \"-lm\" # 链接 math 库)

3.跨平台兼容性

同一代码在不同系统(如 Linux、Windows)的编译结果可能不同,需结合 if(UNIX) 或 if(WIN32) 处理:

if(UNIX) # 检查 Linux 特有的系统调用 check_c_source_compiles(\"...\" HAVE_LINUX_FEATURE)elseif(WIN32) # 检查 Windows 特有的 API check_c_source_compiles(\"...\" HAVE_WIN32_FEATURE)endif()

3.5.CheckCXXSourceCompiles 

用法、原理都和CheckCSourceCompiles差不多,就不在这里赘述了。

4.检查编译器特性

4.1.CheckCXXCompilerFlag(C++ 编译器标志)

4.1.1.简介

        CheckCXXCompilerFlag 是 CMake 提供的一个模块,用于检查 C++ 编译器是否支持指定的编译选项(flag),并根据检查结果设置相应的变量。它在跨平台 C++ 项目中非常实用,因为不同编译器(如 GCC、Clang、MSVC)支持的编译选项往往存在差异,通过该模块可以确保只使用当前编译器支持的选项。

        基本语法:

check_cxx_compiler_flag( )
  • :要检查的 C++ 编译器选项(如 -Wall-Wextra/W4` 等)。
  • :输出变量名,若编译器支持该选项则设为 1TRUE),否则为 0FALSE)。

4.1.2.使用前提

需先加载 CheckCXXCompilerFlag 模块:

include(CheckCXXCompilerFlag) # 加载模块

基本用法示例:

# 加载模块include(CheckCXXCompilerFlag)# 检查常见警告选项是否支持check_cxx_compiler_flag(\"-Wall\" CXX_SUPPORTS_WALL) # 启用基本警告check_cxx_compiler_flag(\"-Wextra\" CXX_SUPPORTS_WEXTRA) # 启用额外警告check_cxx_compiler_flag(\"-Wpedantic\" CXX_SUPPORTS_WPEDANTIC) # 严格遵循标准# 检查 C++ 标准选项(如 C++17)check_cxx_compiler_flag(\"-std=c++17\" CXX_SUPPORTS_CXX17)# 检查 MSVC 特有的选项(如 /W4 警告级别)if(MSVC) check_cxx_compiler_flag(\"/W4\" CXX_SUPPORTS_W4)endif()# 根据检查结果设置编译选项if(CXX_SUPPORTS_WALL) target_compile_options(myapp PRIVATE \"-Wall\")endif()if(CXX_SUPPORTS_WEXTRA) target_compile_options(myapp PRIVATE \"-Wextra\")endif()# 设置 C++ 标准if(CXX_SUPPORTS_CXX17) target_compile_features(myapp PRIVATE cxx_std_17)else() message(WARNING \"编译器不支持 C++17,将使用较低标准\")endif()

4.1.3.进阶用法

1.结合不同编译器设置选项

不同编译器的选项名称可能不同(如警告选项:GCC 用 -W 前缀,MSVC 用 /W 前缀),可结合编译器类型适配:

include(CheckCXXCompilerFlag)# GCC/Clang 特有的警告选项if(CMAKE_CXX_COMPILER_ID MATCHES \"GNU|Clang\") check_cxx_compiler_flag(\"-Wshadow\" CXX_SUPPORTS_WSHADOW) # 检查变量遮蔽警告 if(CXX_SUPPORTS_WSHADOW) target_compile_options(myapp PRIVATE \"-Wshadow\") endif()endif()# MSVC 特有的选项if(MSVC) check_cxx_compiler_flag(\"/permissive-\" CXX_SUPPORTS_PERMISSIVE) # 禁用非标准扩展 if(CXX_SUPPORTS_PERMISSIVE) target_compile_options(myapp PRIVATE \"/permissive-\") endif()endif()

2.检查优化选项

验证编译器是否支持特定优化选项(如 -O3-march=native):

# 检查是否支持 -O3 优化check_cxx_compiler_flag(\"-O3\" CXX_SUPPORTS_O3)if(CXX_SUPPORTS_O3) target_compile_options(myapp PRIVATE \"$<$:-O3>\")endif()# 检查是否支持 CPU 原生指令优化(GCC/Clang)if(CMAKE_CXX_COMPILER_ID MATCHES \"GNU|Clang\") check_cxx_compiler_flag(\"-march=native\" CXX_SUPPORTS_MARCH_NATIVE) if(CXX_SUPPORTS_MARCH_NATIVE) target_compile_options(myapp PRIVATE \"-march=native\") endif()endif()

4.1.4.注意事项

1.选项的上下文相关性

部分选项仅在特定场景下有效(如 -fsanitize=address 需要编译器支持地址 sanitizer),CheckCXXCompilerFlag 仅检查选项是否被编译器识别,不验证其功能是否可用。

2.区分编译选项和链接选项

该模块仅检查编译阶段的选项(如 -Wall-std=c++17),若需检查链接阶段的选项(如 -fsanitize=address 通常需要同时作为链接选项),需结合 CheckLinkerFlag 模块:

include(CheckCXXCompilerFlag)include(CheckLinkerFlag)# 检查地址 sanitizer 编译和链接选项check_cxx_compiler_flag(\"-fsanitize=address\" CXX_SUPPORTS_ASAN)check_linker_flag(\"-fsanitize=address\" LINK_SUPPORTS_ASAN)if(CXX_SUPPORTS_ASAN AND LINK_SUPPORTS_ASAN) target_compile_options(myapp PRIVATE \"-fsanitize=address\") target_link_options(myapp PRIVATE \"-fsanitize=address\")endif()

3.缓存结果

检查结果会被缓存到 CMakeCache.txt 中,若需重新检查,需删除缓存或使用 CMAKE_FORCE_REGENERATE 强制重新生成。

4.2.CheckCCompilerFlag(C编译器标志)

用法、原理都和CheckCXXCompilerFlag差不多,就不在这里赘述了。

4.3.CheckLinkerFlag(链接器标志)

4.3.1.简介

        CheckLinkerFlag 是 CMake 提供的一个模块,用于检查链接器是否支持指定的链接选项(linker flag),并根据检查结果设置相应的变量。它在跨平台项目中非常实用,因为不同链接器(如 GNU ld、Clang lld、MSVC link.exe 等)支持的链接选项往往存在差异,通过该模块可以确保只使用当前链接器支持的选项。

        基本语法:

check_linker_flag(  )
  • :指定语言(C 或 CXX),用于确定使用的链接器(通常与编译器关联)。
  • :要检查的链接选项(如 -Wl,--as-needed-fsanitize=address/DEBUG 等)。
  • :输出变量名,若链接器支持该选项则设为 1TRUE),否则为 0FALSE)。

4.3.2.使用前提

需先加载 CheckLinkerFlag 模块:

include(CheckLinkerFlag) # 加载模块

4.3.3.基本用法示例

1.检查通用链接选项

# 加载模块include(CheckLinkerFlag)# 检查链接器是否支持 --as-needed(控制动态库按需链接,GNU ld 特性)check_linker_flag(C \"-Wl,--as-needed\" LINKER_SUPPORTS_AS_NEEDED)# 检查是否支持地址 sanitizer 链接选项(需编译器和链接器同时支持)check_linker_flag(CXX \"-fsanitize=address\" LINKER_SUPPORTS_ASAN)# 根据检查结果设置链接选项add_executable(myapp main.cpp)if(LINKER_SUPPORTS_AS_NEEDED) target_link_options(myapp PRIVATE \"-Wl,--as-needed\")endif()if(LINKER_SUPPORTS_ASAN) # 地址 sanitizer 通常需要同时设置编译和链接选项 target_compile_options(myapp PRIVATE \"-fsanitize=address\") target_link_options(myapp PRIVATE \"-fsanitize=address\")endif()

2.适配不同平台的链接选项

不同平台的链接器选项差异较大(如 Linux 使用 -Wl,xxx,Windows MSVC 使用 /xxx),需结合平台类型处理:

include(CheckLinkerFlag)add_executable(platform_app src/main.c)# Linux 平台:检查 ld 特有的选项if(UNIX AND NOT APPLE) check_linker_flag(C \"-Wl,--no-undefined\" LINKER_SUPPORTS_NO_UNDEFINED) if(LINKER_SUPPORTS_NO_UNDEFINED) target_link_options(platform_app PRIVATE \"-Wl,--no-undefined\") # 禁止未定义符号 endif()endif()# Windows MSVC 平台:检查 link.exe 特有的选项if(MSVC) check_linker_flag(CXX \"/DEBUG\" LINKER_SUPPORTS_DEBUG) # 生成调试信息 if(LINKER_SUPPORTS_DEBUG) target_link_options(platform_app PRIVATE \"$<$:/DEBUG>\") endif()endif()# macOS 平台:检查 ld64 特有的选项if(APPLE) check_linker_flag(C \"-Wl,-dead_strip\" LINKER_SUPPORTS_DEAD_STRIP) # 移除未使用符号 if(LINKER_SUPPORTS_DEAD_STRIP) target_link_options(platform_app PRIVATE \"-Wl,-dead_strip\") endif()endif()

3.检查与编译器关联的链接选项

部分选项需要编译器和链接器同时支持(如 sanitizer、链接时优化等),需结合 CheckCCompilerFlag 或 CheckCXXCompilerFlag 一起使用:

include(CheckLinkerFlag)include(CheckCXXCompilerFlag)add_executable(optimized_app src/performance.cpp)# 检查链接时优化(LTO)选项check_cxx_compiler_flag(\"-flto\" CXX_SUPPORTS_LTO)check_linker_flag(CXX \"-flto\" LINKER_SUPPORTS_LTO)if(CXX_SUPPORTS_LTO AND LINKER_SUPPORTS_LTO) # 同时设置编译和链接选项 target_compile_options(optimized_app PRIVATE \"-flto\") target_link_options(optimized_app PRIVATE \"-flto\") message(STATUS \"启用链接时优化(LTO)\")endif()

4.3.4.注意事项

1.区分链接选项和编译选项

链接选项(如 -lm-Wl,--as-needed)用于控制链接过程,与编译选项(如 -Wall-O3)不同,需用 CheckLinkerFlag 单独检查。

2.选项的平台相关性

链接选项高度依赖平台和链接器类型:

  • GNU 链接器(ld)常用 -Wl,option 传递选项(如 -Wl,--no-undefined);
  • MSVC 链接器(link.exe)使用 /option 格式(如 /DEBUG/OPT:REF);
  • macOS 链接器(ld64)常用 -Wl,-option 格式(如 -Wl,-dead_strip)。

3.与编译器选项的协同

部分功能(如 sanitizer、LTO)需要同时设置编译和链接选项,且两者必须匹配,需同时检查编译器和链接器是否支持。

4.避免不必要的检查

通用链接选项(如 -lm 链接数学库)在对应平台上通常受支持,无需检查;仅对平台特定或高级选项(如 -flto--as-needed)进行验证。

5.总结

适用场景对比:

工具类别 典型工具 核心用途 头文件检查 check_include_file_cxx 验证 C++ 头文件是否存在 符号 / 函数检查 check_cxx_symbol_exists 验证 C++ 符号(含命名空间、模板)是否存在 库函数检查 CheckLibraryExists 验证指定库中是否存在某个函数(需处理修饰) 类型 / 关键字检查 CheckCXXTypeExists 验证 C++ 类型或关键字是否被编译器支持 编译器 / 链接器特性检查 CheckCXXCompilerFlag 验证编译器是否支持特定编译选项 库 / 程序存在性检查 find_libraryfind_program 查找库文件或可执行程序的路径

这些工具共同构成了 CMake 的跨平台检查体系,可根据具体需求(如检查头文件、验证函数、确认编译器特性等)选择合适的工具,确保项目在不同环境下的兼容性。

相关链接

  • CMake 官网 CMake - Upgrade Your Software Build System
  • CMake 官方文档:CMake Tutorial — CMake 4.1.0-rc3 Documentation
  • CMake 源码:https://github.com/Kitware/CMake
  • CMake 源码:CMake · GitLab
  • 中文版基础介绍: CMake 入门实战 | HaHack
  • wiki: Home · Wiki · CMake / Community · GitLab
  • Modern CMake 简体中文版:  Introduction · Modern CMake