CMake
CMake是一个跨平台安装/编译工具,它能用简单的语句描述所有平台的安装/编译过程。通常在UNIX环境下,CMake根据CMakeLists.tx生成Makefike,在Makefile中定义了具体的编译过程。
CMake指令是大小写无关的,即不区分大小写,但建议全部使用大写指令
变量是大小写相关的,使用 ${}方式取值。
生成可执行程序格式
cmake_minimum_required(VERSION 3.10) # 设定最低版本要求
project(项目名称)
set(CMAKE_CXX_STANDARD 17) # 使用C++ 17标准
#set(CMAKE_CXX_STANDARD 14) # 使用C++ 14标准
# 将main.cpp编译成可执行文件MyExecutable
add_executable(MyExecutable main.cpp)
以上是最基础的生成可执行文件CMakeLists.txt的写法
具体了解可见基本命令
绿色的就是可执行程序,在win上后缀名为.exe的。
程序编译的整体流程
以下面一个简单的程序介绍: 这是一个名为main.cpp的源文件
#include <iostream>
using namespace std;
int main(){
cout<<"nihao"<<endl;
return 0;
}
为了在系统上运行这个程序,每条C语句都必须被其他程序转化为一系列的低级机器语 言指令,然后这些指令按照一种为可执行目标程序的格式打好包,并以二进制磁盘文件 的形式存放起来。目标程序也称为可执行目标文件。
GCC编译器驱动程序读取源程序文件main.c,并把它翻译成一个可执行目标文件main。 这个翻译过程分为四个阶段:预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)、链接(Linking)。执行这四个阶段的程序(预处理器、编译器、汇编器、和链接器)一起构成了编译系统。
预处理阶段
预处理器(cpp)将所有的宏(#define)删除,并展开所有的宏定义。
处理所有的条件预编译指令,比如#ifdef、#elif、#else、#endif等。
处理#include预编译指令,将所有被包含的文件直接插入到预编译指令的位置。
添加行号和文件标识,以便编译时产生调试用的行号及编译错误行号。
保留所有的#pragma编译器指令。因为编译器需要使用他们。
使用gcc -E main.cpp -o main.i命令来进行预处理,预处理得到的另一个程序通常以,i作为扩展名
汇编阶段
编译器(ccl)将预处理完的文本文件hello.i进行一系列的词法分析、语法分析、语义分析和优化,翻译成文本文件hello.s,它包含一个汇编语言程序。
链接阶段
链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时(compile time),也就是 在源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被 加载器(loader)加载到内存并执行时;甚至执行于运行时(run time),也就是由应用程序来执行。链接是由叫链接器(linker)的程序自动执行的。
动态链接共享库
为什么要有动态链接和共享库呢?
静态库和所有的软件一样,需要定期的维护和更新。如果程序员想要使用一个库的最新版本,他们必须以某种方式了解到该库的更新情况,然后显示地将他们的程序与更新的库重新链接。
考虑内存和磁盘空间。静态链接极大地浪费内存空间。因为在静态链接的情况下,假设有两个程序共享一个模块,那么在静态链接后输出的两个可执行文件中各有一个共享模块的副本。如果同时运行这两个可执行文件,那么这个共享模块将在磁盘 和内存中都有两个副本,对磁盘和内存造成极大地浪费。
共享库(shared library)是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序 链接起来。这个过程称为动态链接(dynamic linking),是由一个叫动态链接器 (dynamic linker)的程序来执行的。 共享库“共享的”方式
在任何给定的文件系统中,对于一个库只有一个.so文件。所有引用该库的可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库的内容那样被复制和嵌入到引用它们的可执行文件中。
一个.text节的一个副本可以被不同的正在运行的进程共享。
环境搭建
#安装cmake
sudo apt install cmake
#查看
cmake -version
简单入门
还是用以上的main.cpp和CMakeLists.txt为例
#首先,创建CMakeLists,注意大小写
touch CMakeLists.txt
#然后创建一个文件夹build,用来存储cmake生成的文件
mkdir build && cd build
cmake ..
make
#最终生成可执行文件MyExecutable
#当然你可以使用tree来查看项目的架构
基本命令
cmake_minimum_required - 指定CMake最小版本要求
cmake_minimum_required(VERSION 3.21.0)
project - 定义工程名称,并可指定工程支持的语言
project(nihao)
set - 显示的定义变量
#定义变量,其值为sayhello.cpp hello.cpp
set(SRC sayhello.cpp hello.cpp)
include_directories - 向工程添加多个特定的头文件搜索路径 --->相当于g++编译器-l参数
#将/usr/lib/myincludefolder 和 ./include 添加到头文件搜索路径
include_directories(/usr/include/myincludefolder ./include)
link_directories - 向工程添加多个特定的库文件搜索路径 --->相当于g++编译器的-L参数
#将/usr/lib/myfoldfer 和 ./lib 添加到库文件搜索路径
link_directories(/usr/lib/mylibfolder ./lib)
add_library - 生成库文件
#通过变量SRC生成 libhello.so 共享库
add_library(hello SHARED ${SRC})
add_executable - 生成可执行文件
add_executable(main main.cpp)
target_link_libraries - 为target添加需要链接的共享库 -->相当于指定g++编译器-l参数
#将hello动态库文件链接到可执行文件main
target_link_libraries(main hello)
aux_source_directory - 发现一个目录下所有的源代码文件并将列表储存到一个变量中,这个指令临时被用来构建文件列表
#定义SRC变量,其值为当前下所有的源代码文件,有点类似于set
aux_source_directory(. SRC)
add_subdirectory - 用于将一个子目录及其构建文件
add_subdirectory(src)
add_subdirectory(lib)
file(GLOB ...) - 用于匹配文件路径模式,并将匹配的文件列表存储在变量中。
file(GLOB <variable> [RELATIVE <path>] [<globbing expressions>])
#<variable>:用于存储匹配到的文件路径的变量。
#RELATIVE <path>:指定相对路径,如果提供了 <path>,则结果会相对该路径进行返回。
#<globbing expressions>:匹配文件的模式,比如 .cpp、.h 等。
CMake常用变量
CMAKE_C_FLAGS gcc编译选项
CMAKE_CXX_FLAGS g++编译选项
有兴趣可以去搜搜gcc和g++的区别
#在CMAKE_CXX_FLAGS编译选项后追加 -pthread 表示启用 POSIX 线程(pthreads)库的支持 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
CMAKE_BUILD_TYPE 编译类型 (Debug,Release)
#设定编译类型为debug,调试时需要选择debug set(CMAKE_BUILD_TYPE Debug) #设定编译类型为release,调试时需要选择release set(CMAKE_BUILD_TYPE Release)
还有许多,感兴趣可以去查找,学海无涯(。・∀・)ノ゙
CMake编译工程
一项工程一般都包含了一下文件夹
include、src、主函数文件main.cpp
如图
# 最低CMake版本要求 cmake_minimum_required(VERSION 3.10) # 工程名称 project(MyProject) # 设置C++标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) # 包含头文件目录 include_directories(${CMAKE_SOURCE_DIR}/include) # 查找 src 目录中的所有源文件 file(GLOB SOURCES ${CMAKE_SOURCE_DIR}/src/*.cpp) # 添加可执行文件 add_executable(my_executable main.cpp ${SOURCES}) # 如果需要链接其他库,使用 target_link_libraries # target_link_libraries(my_executable some_library)
如图
顶层CMakeLists.txt
cmake_minimum_required(VERSION 3.10) project(MyProject) # 设置 C++ 标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) # 添加子目录 add_subdirectory(src) add_subdirectory(include) add_subdirectory(lib) # 添加主程序的可执行文件 add_executable(my_executable main.cpp) # 链接库到可执行文件 target_link_libraries(my_executable PRIVATE my_library)
src/CMakeLists.txt文件
# 查找 src 目录下的所有源文件 file(GLOB SRC_SOURCES *.cpp) # 创建静态库或动态库 add_library(my_library STATIC ${SRC_SOURCES}) # 公开头文件目录 target_include_directories(my_library PUBLIC ${CMAKE_SOURCE_DIR}/include)
include/CMakeLists.txt文件
# 暴露 include 目录给其他目标 # 如果 include/CMakeLists.txt 是空的,这里可以不需要任何内容 target_include_directories(my_library PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
lib/CMakeLists.txt文件
# 查找 lib 目录下的所有源文件 file(GLOB LIB_SOURCES *.cpp) # 创建静态库或动态库 add_library(my_lib STATIC ${LIB_SOURCES}) # 公开头文件目录 target_include_directories(my_lib PUBLIC ${CMAKE_SOURCE_DIR}/include)
感想
这边觉得一份代码要好管理,那就需要规范。
首先要做到就是代码的架构要清晰明了
之前就是做到不好(刚学的时候,这些命令一团浆糊),想着就是代码能跑就行
为了后来人更好理解cmake,更好的整理代码,所以开始整理了相关知识点。
当然,你开源的时候代码架构会好看
参考:【基于VSCode和CMake实现C/C++开发 | Linux篇】https://www.bilibili.com/video/BV1fy4y1b7TC?vd_source=2d04e92b3f2ecec4691c618cd87eb918
如有错误,记得及时联系作者,毕竟作者也是个小老登
2024.9.9
作者:郑臻
qq:815641266