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
表示线程库)。:要检查的函数名(如
sqrt
、pthread_create
)。:包含该函数声明的头文件路径(可指定多个,用分号分隔)。
:输出变量名,若找到函数则设为
1
(TRUE
),否则为0
(FALSE
)。
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),直接使用函数名可能无法匹配。此时需:
- 先通过
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
)
- 原因:库不在系统默认路径,且未通过
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 符号(函数名、变量名等,如
printf
、O_CREAT
)。:包含该符号声明的头文件(多个头文件用分号分隔,如
\"stdio.h;stdlib.h\"
)。:输出变量名,若符号存在则设为
1
(TRUE
),否则为0
(FALSE
)。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 编译器(如 gcc
、cl.exe
)编译上述代码,若编译成功(无 “未声明的标识符” 错误),说明符号存在;若编译失败,则符号不存在。
3.设置结果变量
根据编译结果,将 设为
1
(成功)或 0
(失败),并缓存结果到 CMakeCache.txt
。
2.2.4.与 CheckLibraryExists
的区别
check_c_symbol_exists
CheckLibraryExists
m
、pthread
)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 函数名(如
malloc
、getpid
)。:输出变量名,若函数存在则设为
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( [BUILTIN_TYPES_ONLY] [LANGUAGE ] [FLAGS ])
:要检查的类型名称(如
int
、size_t
、my_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::vector
、my::custom_type
)。:包含该类型声明的头文件(多个头文件用分号分隔,如
\"vector;string\"
)。:输出变量名,若类型存在则设为
1
(TRUE
),否则为0
(FALSE
)。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.类型需完整声明
目标类型必须在指定的头文件中完整声明(如 class
、struct
或 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++ 关键字(如 constexpr
、decltype
、concepts
等),并根据检查结果设置相应的变量。它通过编译一段包含目标关键字的极简代码,验证编译器对该关键字的支持程度,是跨平台 C++ 项目中处理语言特性兼容性的重要工具。
基本语法:
check_cxx_keyword_exists( [FLAGS ])
:要检查的 C++ 关键字(如
constexpr
、requires
、nullptr
)。:输出变量名,若编译器支持该关键字则设为
1
(TRUE
),否则为0
(FALSE
)。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
函数)。:输出变量名,若代码编译成功则设为
1
(TRUE
),否则为0
(FALSE
)。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` 等)。:输出变量名,若编译器支持该选项则设为
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 \"$<$:-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
等)。:输出变量名,若链接器支持该选项则设为
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 \"$<$:/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
check_cxx_symbol_exists
CheckLibraryExists
CheckCXXTypeExists
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