- 跨平台的“编译器所需文件的生成器”
- Make是什么
- 相当于自动执行多个文件的编译与链接
- 只需一个配置文件,就可根据配置文件自动生成命令然后执行
- 同时应用了增量编译的思想:文件没有做出修改就不会重新编译
- 只显示自动生成的编译命令而不运行:
make --dry-run
- Make有什么缺点?
- 语法不够复杂,许多高级功能实现不了
- GNU系软件,没有官方的对Windows的支持
- 与GCC结合紧密,但与其他编译器结合可能会出现问题
- 需要明确指明项目与项目间(或项目与子模块)的依赖关系
- CMake解决了这些问题
- 高级和复杂的语法,适应多种情况
- 自动检测源文件和头文件的关联
- 针对不同平台的不同编译器生成不同的项目文件/编译命令
相关概念
编译模式
头文件
- 对于C++来说:
- C++是强声明的语言,在没有声明时 是不知道一个名字是变量、函数还是类的
#include
是预处理命令,本质上就是文件的插入,相当于把被include的文件的内容复制粘贴过来
- 因此会遇到 重复定义问题、循环包含问题 和 include顺序不同导致编译结果不一样 等问题
- 解决办法:
- include顺序固定
- 使用宏
#pragma once
- 使用宏
#ifdef
、#ifndef
- 使用模块(C++20)
- 两种include的语法
- 双引号
#include ""
:先找工作目录,再找系统目录
- 尖括号
#include <>
:直接找系统目录
- 为什么有双引号include方式还要保留尖括号方式?:可以避免工作目录内同名的其他文件
<cxxx>
和<xxx.h>
没有必然联系,有些头文件中会检测当前语言是否为C++,是的话自动添加extern "C"{}
库
- 分为静态库和动态库
- 静态库链接时会直接插入到程序中,动态库则只会向程序中插入“插桩”代码(PLT表)
- 动态链接库的查找位置
- Windows:程序同文件夹 -> PATH内目录(用户PATH -> 系统PATH)
- *nix:ELF程序的RPATH ->
/usr/lib
-> /lib
命令
cmake --version
:当前CMake版本号
cmake -H<path1> -B<path2>
:使用CMake生成文件。在path1中查找CMakeLists.txt,在path2子目录中存放所有编译文件
cmake -D<flag>
:设置CMake编译选项
cmake -G<generator>
:切换CMake生成针对哪个编译器的项目文件
- [[TODO]]
函数
- 函数名是不区分大小写的,但变量名/关键字是区分大小写的。关键字一般都是大写,变量名(除系统定义的之外)建议小写
project(xxx)
:指定项目名
- 设置项目语言:
project(xxx LANGUAGES CXX)
;CXX
:即C++
1
| project(hello_world LANGUAGES CXX)
|
cmake_minimum_required(VERSION xxx)
:指定最低CMake版本号
1
| cmake_minimum_required(VERSON 3.22)
|
add_executable(AAA, BBB)
:把BBB文件编译成AAA,AAA是目标文件名
1
| add_executable(hello.elf hello.cc)
|
set(AAA, BBB)
:把AAA变量(定义并)内容设置为BBB
file()
:查找文件,并将结果写入变量
target_include_directories()
:相当于cmake -I
,设置头文件查找文件夹
1 2 3 4 5
| target_include_directories(target PRIVATE ${PROJECT_SOURCE_DIR}/include )
|
如何给项目添加多个文件?
将多个文件编译为一个目标时,可使用如下方法
- 直接在
add_executable
里指定文件,add_executable(xxx a.cc b.cc c.h)
1 2 3 4 5
| add_executable(${PROJECT_NAME} main.cc hello.cc )
|
- 使用变量,不推荐。
set(YYY a.cc b.cc c.h) add_executable(XXX ${YYY})
1 2 3 4 5 6 7 8 9 10 11
| set(SOURCES main.cc)
set(SOURCES main.cc hello.cc )
add_executable(${PROJECT_NAME} ${SOURCES})
|
- 使用
file()
函数动态检测,即使用通配符的自动添加,不推荐:顺序不固定,且易丢失文件。
1 2 3 4
|
file(GLOB SOURCES "*.cc") add_executable(${PROJECT_NAME} ${SOURCES})
|
变量
PROJECT_NAME
:由project()
产生
CMAKE_SOURCE_DIR
:源码目录
CMAKE_BINARY_DIR
:编译目录
- 子文件夹相关变量
CMAKE_CURRENT_SOURCE_DIR
:当前源码目录
CMAKE_CURRENT_BINARY_DIR
:当前编译目录
- 子模块相关变量
PROJECT_SOURCE_DIR
:当前项目源码目录
PROJECT_BINARY_DIR
:当前项目编译目录
子模块
- 可以将不同模块的源码放置在不同的子目录下
- 头文件除外。头文件应当放置在同一个目录下,如
include/
- 头文件的目录也可以拥有子目录,如
include/hello/
- 主项目
CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| add_library(hello_library STATIC hello/hello.cc )
target_include_directories(hello_library PUBLIC ${PROJECT_SOURCE_DIR}/include )
add_executable(hello.elf main.cc )
target_link_libraries(hello.elf PRIVATE hello_library )
|
头文件在子文件夹内怎么办?
- 有如下的子模块和文件
1 2 3 4
| main.cc CMakeLists.txt mylib/hello.cc mylib/hello.h
|
A. 可以不在CMakeLists.txt
文件中写target_include_directories()
main.cc
中可以按如下方式引入mylib/hello.h
:#include "mylib/hello.h"
B. 也可以将target_include_directories()
写成
1 2 3 4
| target_include_directories(hello_library PUBLIC ${PROJECT_SOURCE_DIR}/mylib )
|
main.cc
中可以按如下方式引入mylib/hello.h
:#include "hello.h"
或是#include <hello.h>
- 有如下的子模块和文件
1 2 3 4
| main.cc CMakeLists.txt mylib/hello.cc include/mylib/hello.h
|
- 如下编写
target_include_directories()
1 2 3 4
| target_include_directories(hello_library PUBLIC ${PROJECT_SOURCE_DIR}/include )
|
main.cc
中按如下方式引入mylib/hello.h
:#include "mylib/hello.h"