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(<library> <function> <include> <resultVar>)
<library>
:要检查的库名称(如m
表示数学库,pthread
表示线程库)。<function>
:要检查的函数名(如sqrt
、pthread_create
)。<include>
:包含该函数声明的头文件路径(可指定多个,用分号分隔)。<resultVar>
:输出变量名,若找到函数则设为1
(TRUE
),否则为0
(FALSE
)。
2.1.2.使用前提
需先通过 include
命令加载 CheckLibraryExists
模块:
include(CheckLibraryExists) # 加载模块
基本用法示例:
# 加载模块
include(CheckLibraryExists)# 检查数学库(m)中是否存在 sqrt 函数,依赖头文件 math.h
check_library_exists(m sqrt "math.h" HAVE_SQRT_IN_M_LIB)# 检查 pthread 库中是否存在 pthread_create 函数,依赖头文件 pthread.h
check_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),直接使用函数名可能无法匹配。此时需:
- 先通过
nm
或objdump
工具查看库中实际的符号名(如_ZN5boost6system...
); - 在
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 <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++ 函数名被修饰,与传入的
<function>
不匹配。 - 解决:
使用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(<symbol> <headers> <resultVar> [FLAGS <flags>])
<symbol>
:要检查的 C 符号(函数名、变量名等,如printf
、O_CREAT
)。<headers>
:包含该符号声明的头文件(多个头文件用分号分隔,如"stdio.h;stdlib.h"
)。<resultVar>
:输出变量名,若符号存在则设为1
(TRUE
),否则为0
(FALSE
)。FLAGS <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_FUNCFLAGS "-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 <stdio.h> // 用户指定的头文件
int main(void) {(void)printf; // 引用目标符号(仅编译检查,不执行)return 0;
}
2.编译测试代码
使用 C 编译器(如 gcc
、cl.exe
)编译上述代码,若编译成功(无 “未声明的标识符” 错误),说明符号存在;若编译失败,则符号不存在。
3.设置结果变量
根据编译结果,将 <resultVar>
设为 1
(成功)或 0
(失败),并缓存结果到 CMakeCache.txt
。
2.2.4.与 CheckLibraryExists
的区别
特性 | check_c_symbol_exists | CheckLibraryExists |
---|---|---|
检查阶段 | 仅编译阶段(无需链接) | 需链接阶段(验证库中符号) |
适用符号类型 | C 函数、宏、变量(声明即可) | 库中的函数(需实际定义) |
依赖库 | 不依赖具体库(仅需头文件) | 必须指定库(如 m 、pthread ) |
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_ASPRINTFFLAGS "-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_GETLINEFLAGS "-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(<function> <resultVar>)
<function>
:要检查的 C 函数名(如malloc
、getpid
)。<resultVar>
:输出变量名,若函数存在则设为1
(TRUE
),否则为0
(FALSE
)。
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 ) | 检查系统默认库中的函数(如 malloc ) |
CheckLibraryExists | 需指定具体库(如 m 、pthread ) | 检查特定库中的函数(如 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 项目中,它常被用于验证基础系统调用(如 getpid
、malloc
)是否可用,确保代码兼容性。
3.检查类型/关键字/表达式有效性
3.1.CheckTypeSize
(类型大小)
3.1.1.简介
CheckTypeSize
是 CMake 提供的一个模块,用于检查指定数据类型(如 int
、long
或自定义类型)在当前编译环境中的大小(以字节为单位),并将结果存储在变量中。这对于跨平台开发尤为重要,因为不同架构(如 32 位 / 64 位)或编译器可能对同一数据类型分配不同的内存空间。
基本语法:
check_type_size(<type> <resultVar> [BUILTIN_TYPES_ONLY] [LANGUAGE <lang>] [FLAGS <flags>])
<type>
:要检查的类型名称(如int
、size_t
、my_custom_type
)。<resultVar>
:输出变量名,存储类型的大小(以字节为单位);若类型不存在,变量值为0
。BUILTIN_TYPES_ONLY
(可选):仅检查编译器内置类型(如int
),不检查自定义类型。LANGUAGE <lang>
(可选):指定检查使用的语言(C
或CXX
,默认C
)。FLAGS <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 <stddef.h>
int main() {return sizeof(int); // 编译时计算类型大小
}
2.编译并运行测试程序
编译测试代码生成可执行文件,运行该程序后,其返回值即为类型的大小(字节数)。
3.解析结果并设置变量
CMake 捕获程序返回值,将其存储到 <resultVar>
中(如 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_64BITtypedef long long int64_t; // 64 位系统使用 long long
#elsetypedef long int64_t; // 32 位系统兼容处理
#endif
3. 类型依赖特定编译标准
某些类型(如 C++11 的 std::array
)依赖特定语言标准,未指定会导致检查失败。需要通过 FLAGS
指定编译标准:
check_type_size("std::array<int, 10>" 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(<type> <headers> <resultVar> [FLAGS <flags>])
<type>
:要检查的 C++ 类型(如std::vector<int>
、my::custom_type
)。<headers>
:包含该类型声明的头文件(多个头文件用分号分隔,如"vector;string"
)。<resultVar>
:输出变量名,若类型存在则设为1
(TRUE
),否则为0
(FALSE
)。FLAGS <flags>
(可选):传递给 C++ 编译器的额外参数(如-std=c++17
、-I/path/to/include
)。
3.2.2.使用前提
需先加载 CheckCXXTypeExists
模块:
include(CheckCXXTypeExists) # 加载模块
基本用法示例:
# 加载模块
include(CheckCXXTypeExists)# 检查标准库类型(如 std::vector<int>,需要包含 vector 头文件)
check_cxx_type_exists("std::vector<int>" "vector" HAVE_STD_VECTOR)# 检查 C++11 引入的 std::thread(需要包含 thread 头文件和 C++11 标准)
check_cxx_type_exists("std::thread" "thread" HAVE_STD_THREADFLAGS "-std=c++11" # 指定 C++11 标准
)# 检查自定义命名空间中的类型(my::custom_type,需要包含 mytype.h)
check_cxx_type_exists("my::custom_type" "mytype.h" HAVE_MY_CUSTOM_TYPEFLAGS "-I${CMAKE_SOURCE_DIR}/include" # 自定义头文件路径
)# 根据检查结果处理
if(HAVE_STD_VECTOR)message(STATUS "找到 std::vector<int>,可使用动态数组功能")
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<int>
时:
#include <vector> // 用户指定的头文件
int main() {(void)static_cast<std::vector<int>*>(nullptr); // 引用目标类型(无实际执行)return 0;
}
2.编译测试代码
使用 C++ 编译器编译上述代码,若编译成功(编译器能识别该类型),说明类型存在;若编译失败(如 “未声明的标识符”),则类型不存在。
3.设置结果变量
根据编译结果,将 <resultVar>
设为 1
(成功)或 0
(失败),并缓存结果到 CMakeCache.txt
。
3.2.4.注意事项
1.类型需完整声明
目标类型必须在指定的头文件中完整声明(如 class
、struct
或 typedef
定义),否则编译会失败。
2.依赖 C++ 标准
部分类型(如 std::optional
需 C++17)依赖特定 C++ 标准,需通过 FLAGS
指定(如 -std=c++17
):
check_cxx_type_exists("std::optional<int>" "optional" HAVE_STD_OPTIONALFLAGS "-std=c++17"
)
3.命名空间和模板类型
检查带命名空间或模板的类型时,需完整写出类型名(如 std::map<std::string, int>
),并确保头文件包含正确:
check_cxx_type_exists("std::map<std::string, int>" "map;string" # 需包含 map 和 string 头文件HAVE_STD_MAP_STRING_INT
)
3.3.CheckCXXKeywordExists
(C++ 关键字)
3.3.1.简介
CheckCXXKeywordExists
是 CMake 提供的一个模块,专门用于检查 C++ 编译器是否支持指定的 C++ 关键字(如 constexpr
、decltype
、concepts
等),并根据检查结果设置相应的变量。它通过编译一段包含目标关键字的极简代码,验证编译器对该关键字的支持程度,是跨平台 C++ 项目中处理语言特性兼容性的重要工具。
基本语法:
check_cxx_keyword_exists(<keyword> <resultVar> [FLAGS <flags>])
<keyword>
:要检查的 C++ 关键字(如constexpr
、requires
、nullptr
)。<resultVar>
:输出变量名,若编译器支持该关键字则设为1
(TRUE
),否则为0
(FALSE
)。FLAGS <flags>
(可选):传递给 C++ 编译器的额外参数(如-std=c++20
指定语言标准)。
3.3.2.使用前提
需先加载 CheckCXXKeywordExists
模块:
include(CheckCXXKeywordExists) # 加载模块
基本用法示例:
# 加载模块
include(CheckCXXKeywordExists)# 检查 C++11 关键字 nullptr
check_cxx_keyword_exists("nullptr" HAVE_NULLPTR)# 检查 C++11 关键字 constexpr(需指定 C++11 及以上标准)
check_cxx_keyword_exists("constexpr" HAVE_CONSTEXPRFLAGS "-std=c++11" # 明确指定 C++ 标准
)# 检查 C++20 关键字 requires(用于概念约束)
check_cxx_keyword_exists("requires" HAVE_REQUIRES_KEYWORDFLAGS "-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_CONSTEXPRFLAGS "-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_CONSTEXPRconstexpr int max_size = 1024; // 使用 constexpr
#elseconst int max_size = 1024; // 降级为 const
#endif
2.验证编译器兼容性
在项目初始化阶段检查关键关键字,确保编译器满足最低版本要求:
# 检查 C++17 关键字 if constexpr
check_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(<code> <resultVar> [FLAGS <flags>])
<code>
:要检查的 C 语言代码片段(必须是完整可编译的程序,包含main
函数)。<resultVar>
:输出变量名,若代码编译成功则设为1
(TRUE
),否则为0
(FALSE
)。FLAGS <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 <stdio.h>int main() {int n = 10;int arr[n]; // C99 变长数组特性arr[0] = 0;return 0;}
" SUPPORTS_C99_VLAFLAGS "-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 <unistd.h>int main() {int fds[2];int ret = pipe2(fds, O_CLOEXEC); // 目标系统调用return ret;}
" HAVE_PIPE2FLAGS "-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_WORKSFLAGS "-I${CMAKE_SOURCE_DIR}/include" # 自定义头文件路径
)if(MY_MACRO_WORKS)message(STATUS "自定义宏 MY_MACRO 行为符合预期")
else()message(FATAL_ERROR "MY_MACRO 定义错误,无法继续编译")
endif()
3.4.4.注意事项
1.代码必须完整
传入的 <code>
必须是可独立编译的 C 程序,至少包含 main
函数。例如,仅写 int x = 5;
会编译失败,需包裹在 main
中。
2.处理依赖和宏定义
若代码依赖特定头文件、宏定义或库,需显式包含或指定:
# 示例:依赖 math 库的代码
check_c_source_compiles("#include <math.h>int main() {double x = sqrt(2.0); // 依赖 libm 库return 0;}
" SQRT_WORKS
FLAGS "-lm" # 链接 math 库
)
3.跨平台兼容性
同一代码在不同系统(如 Linux、Windows)的编译结果可能不同,需结合 if(UNIX)
或 if(WIN32)
处理:
if(UNIX)# 检查 Linux 特有的系统调用check_c_source_compiles("..." HAVE_LINUX_FEATURE)
elseif(WIN32)# 检查 Windows 特有的 APIcheck_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(<flag> <resultVar>)
<flag>
:要检查的 C++ 编译器选项(如-
-Wall、
-Wextra、
/W4` 等)。<resultVar>
:输出变量名,若编译器支持该选项则设为1
(TRUE
),否则为0
(FALSE
)。
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 "$<$<CONFIG:Release>:-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(<lang> <flag> <resultVar>)
<lang>
:指定语言(C
或CXX
),用于确定使用的链接器(通常与编译器关联)。<flag>
:要检查的链接选项(如-Wl,--as-needed
、-fsanitize=address
、/DEBUG
等)。<resultVar>
:输出变量名,若链接器支持该选项则设为1
(TRUE
),否则为0
(FALSE
)。
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 "$<$<CONFIG:Debug>:/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_library 、find_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