5.2 编译基础
5.2.1 编译工作
编译器必须执行以下步骤来创建一个目标文件
1.预处理 2.语言分析 3.汇编 4.优化 5.生成二进制文件
预处理:高级的查找和替换。跨编译单元共享声明。是实际编译前的一步。 语言分析:语法分析,有意义的标记,验证是否符合语言规则。语义分析,类型的正确性检查等等。 汇编:根据平台可用的指令集,把记号翻译成CPU的指令。 优化:一直在发生。 生成二进制文件:将机器代码写入目标文件。
链接:目标文件给连接器,解析外部符号等。
5.2.2 初始配置
target_compile_features() 编译器特性 target_sources() 向已定义的目标添加源 traget_include_directories() 设置预处理器的包含路径 目标的头文件路径 target_compile_definitions() 设置预处理定义 target_compile_options() 设置特定编译器的编译选项 target_precompile_headers() 预编译头文件 遵循: target_...(<target name> <INTERFACE|PUBLIC|PRIVATE> <value>)
5.2.3 管理目标源
使用target_sources
给目标添加源文件,示例如下:
add_executable(main2 main2.cpp)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_sources(main2 PRIVATE gui_linux.cpp)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
target_sources(main2 PRIVATE gui_windows.cpp)
endif ()
5.3 预处理配置
5.3.1 提供包含文件的路径
预处理器的基本特性是能够使用#include指令包含.h文件,包括两种形式
#include <>
和#include""
通常来说,
· 尖括号形式将检查标准包含目录,包括C++库和C库头文件存储在系统中的目录。
· 引号先在当前文件在的目录中搜索包含的文件,然后再目录中查找带尖括号?的目录。
CMake使用target_include_directories(<target> [SYSTEM] [AFTER|BEFORE] <INTERFACE|PUBLIC|PRIVATE> [item1...] ...)
来操作头文件的搜索路径。
5.3.2 通过CMake添加预处理宏定义
#define ABC
#define DEF 8
等价于
add_executable(defined definitions.cpp)
set(VAR 8)
target_compile_definitions(defined PRIVATE ABC "DEF=${VAR}")
这些标志通常以-D传递给编译器
通过宏打印git commit的SHA值
#define str(s)
#define xstr(s) str(s) // convert definitions into c-strings
int main(){
#if defined(SHA)
std::cout << "GIT commit: " << xstr(SHA) << std::endl;
#endif
}
# 打印commit的SHA值
add_executable(print_commit print_commit.cpp)
execute_process(COMMAND git log -1 --pretty=format:%h OUTPUT_VARIABLE SHA)
target_compile_definitions(print_commit PRIVATE "SHA=${SHA}")
5.3.3 配置头文件
上面的方法存在一个问题,如果有多个宏的话,通过target_compile_definitions()
传递变量会比较麻烦。
可以将宏收集起来,书写占位符头文件,然后在CMakeLists.txt中填充值并生成头文件。
// configure.h.in
#cmakedefine FOO_ENABLE
#cmakedefine FOO_STRING1 "@FOO_STRING1@"
#cmakedefine FOO_STRING2 "${FOO_STRING2}"
#cmakedefine FOO_UNDEFINED "FOO_UNDEFINED@"
接着,在CMakeLists.txt中填充这些宏
add_executable(configure configure.cpp)
set(FOO_ENABLLE ON)
set(FOO_STRING1 "abc")
set(FOO_STRING2 "def")
configure_file(configure.h.in configured/configure.h)
target_include_directories(configure PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
主函数如下
#include <iostream>
#include "configured/configure.h"
#define str(s) #s
#define xstr(s) str(s)
int main(){
#ifdef FOO_ENABLE
std::cout << "FOO_ENABLE: ON" << std::endl
#endif
std::cout << "FOO_STRING1: " << xstr(FOO_STRING1) << std::endl;
std::cout << "FOO_STRING1: " << xstr(FOO_STRING2) << std::endl;
std::cout << "FOO_STRING1: " << xstr(FOO_UNDEFINED) << std::endl;
}
最后,cmake会生成如下文件:
#define FOO_ENABLE
#define FOO_STRING1 "abc"
#define FOO_STRING2 "def"
/* FOO_UNDEFINED "@FOO_UNDEFINED@" */
@VAR@
和${VAR}
被值替换,未定义的变...
5.4配置优化器
启用优化时,使用target_compile_options(<target> [BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...]...])
5.4.1优化级别
优化级说明符:-O0 -O1 -O2 -O3
CMAKE_CXX_FLAGS_DEBUG
等于-g
CMAKE_CXX_FLAGS_RELEASE
等于-O3 -DNDEBUG
内建变量的格式通常是CMAKE_<LANG>_FLAGS_<CONFIG>
还可以通过-f
选项添加-finline-functions
启用标志
还可以通过-fno
选项添加-fnoinline-functions
禁用它们
5.4.2 函数内联
5.4.3 循环展开
指将循环转换为一组语句,以达到相同的效果。空间换速度。
GCC上: -floop-unroll
GCC上-O3隐式使用-floop-unroll-and-jam
CLANG上: -funroll-loops
5.4.4 循环向量化
SIMD 单指令多数据
GCC在-O3级别的优化中启用循环的自动向量化,CLANG默认启用。
GCC中的启用循环向量化: -ftree-vectorize
-ftree-slp-vectorize
CLANG中的禁用循环向量化: -fno-vectorize
-fno-slp-vectorize
5.5编译过程
5.5.1 减少编译时间
预编译头文件
target_precompile_headers(<target> <INTERFACE|PUBLIC|PRIVATE> [header1...] [<INTERFACE | PUBLICK | PRIVATE> [header2...] ...])
添加的头文件列表存储在PRECOMPILE_HEADERS
目标属性中。
CMake将所有的头文件的名称放在cmake_pch.h文件中然后预编译为一个特定于编译器的二进制文件 。 使用方法
add_executable(precompiled hello.cpp)
target_precompile_headers(precompiled PRIVATE <iostream>)
//hello.cpp
int main(){
std::cout << "Hello world" << std::endl;
}
main.cpp 文件中,不需要包含cmake_pch.h或任何其他头文件。将由 cmake 使用特定于编译器的命令行选项强制包含。
也可以重用另一个目标的预编译头文件。
target_precompile_headers(<target> REUSE_FROM <other_target>)
还有一种用法:
写一个pch.h文件,在pch.h文件中写入固定使用的一些头文件。然后在其他的文件中直接include "pch.h"文件即可。
在CMakeLists.txt中的写法如下:
target_precompile_headers(<target> PUBLIC ./include/pch.h)
统一构建
针对目标的统一构建属性设置
set_target_properties(<target1> <target2> ... PROPERTIES UNITY_BUILD true)
针对文件的统一构建属性设置
set_property(SOURCE <src1> <src2>... PROPERTY UNITY_GROUP "GroupA")
使用C++20特性 module
export module helloworld;
5.5.2 查找错误
头文件的调试问题
add_executable(debug hello.cpp)
target_compile_options(debug RPIVATE -H)
用于输出包含的头文件的路径。
为调试器提供信息
CMAKE_CXX_FLAGS_DEBUG 包含 -g
CMAKE_CXX_FLAGS_RELEASE 包含 -DNDEBUG
启用release时 assert等面向调试的宏就无法起作用了。 如果一定要使用
#undef NDEBUG
#include <assert>