一.Make
- 什么是Make?
由于在实际的开发过程中,如果只使用gcc命令对程序进行编译的话,效率是非常低的。因为开发的程序往往是有多个原文件组成的,文件非常庞大。
为了提高编译程序的效率,所以提出了make的概念,make是gcc提供的半自动化工程管理器。用户只需要在终端输入或者单击一个"make",就可以启动工程管理器对整个程序进行自动编译。 - make的优越性具体体现在两个方面:使用方便,调试效率高
(1)使用方便。通过命令make就可以进行自动编译了。make启动后会根据Makefile文件中的编译规则命令自动对源文件进行编译和链接,最终生成可执行文件。
(2)调试效率高。为了提高编译程序的效率,make会检查每个源文件的修改时间。只有在上次编译之后被修改的文件才会在接下来的编译过程中被编译和链接,这样避免了多余的编译工作量。这点得提醒下,必须保证操作系统时间的正确性;这样是为了保证源文件具有正确的时间戳(VMWare虚拟机的CMOS时间是否正确)。
二.Makefile
- 什么是Makefile?
Makefile是一个文件,是make工程管理器依赖的编译规则命令文件。 - Makefile由三部分组成:
- 需要生成的目标文件target file
- 生成目标文件所需的依赖文件dependency file
- 生成目标文件的编译规则命令行command
- makefile的编写格式
target file : dependency file
command //makefile在书写command命令行前必须加一个<Tab>键
- make根据makefile编译规则命令的流程
(1)make读取makefile文件
eg:有一个hello的程序,它有hh.c,hi.c和hi.h组成的,它的makefile如下
hello : hh.o hi.o
cc -o hello hh.o hi.o
hh.o : hh.c hi.h
cc -c hh.c
hi.o : hi.c
cc -c hi.c
(2)查找makefile文件中的第一个target file,也就是hello,整个hello就是make编译的最终目的。
(3)把target file:hello的依赖file当作target file进行依赖规则检查。这是一个递归的检查过程,在本例中就是依次把hh.o和hi.o作为目标文件来检查各自的依赖规则。make会根据以下三种情况进行处理:
a. 如果当前目录下没有或缺少依赖file,则执行其规则命令生成依赖file。eg:缺少hh.o文件,则执行cc -c hh.c
生成hh.o
b. 如果存在依赖file,则把其作为target file来检查依赖规则。eg:hh.c更新了,hh.c比hh.o新,则执行cc -c hh.c
来更新hh.o
c. 假如target file比所有依赖file新,则不做出任何处理。
(4)递归执行第三步后,就会得到target file:hello所有最新的依赖file。然后make依然会根据下面三种情况进行处理:
a. 如果target file:hello不存在(列如第一次编译),则执行规则命令生成hello
b. 如果target file:hello存在,但存在比hello要新的依赖file,则执行规则命令更新hello
c. target file:hello存在,且比所有依赖file新,则不做处理
- Makefile的特性
所谓的特性也就是Mafile提供了很多类似高级编程语言的语法机制。因为源文件数量庞大的程序,其编译规则就会很复杂,导致Makefile文件越复杂.所以提出了makefile“特性”。在此,就简单简述一下,有兴趣的读者可以去深入看看。
(1)变量
变量是为了解决文件名重复出现的问题。使用方式为$ (变量名)
;eg:
obj.o = hh.o hi.o //使用变量obj来代替hh.o hi.o
hello : $(obj)
cc -o hello $(obj) //当源文件名发生改动或增删源文件时,只需要对变量obj的值进行修改就可以了
hh.o : hh.c hi.h
cc -c hh.c
hi.o : hi.c
cc -c hi.c
(2)自动推导
很明显,就是简化makefile的书写,这个功能默认每个目标文件都有一个与之对应的依赖文件
这里提出了一个伪目标的概念。
伪目标不是真的目标文件,所用通过伪目标可以让make只执行规则命令,而不用创建实际的目标文件。用.PHONY
来标识。
make 伪目标名 //使用方式
eg:
obj = a.o b.o
.PHONY : all //伪目标名
all : test $(obj) //输入命令 make all之后会make会更新all的依赖文件
.PHONY : clean // 输入:make clean make会执行下面的操作
clean :
rm -rf test $(obj)
test_dir = /home/t_d
.PHONY : install //输入:make install,make会按顺序去执行下面的命令
install : //把test文件复制到test_dir变量指定的目录中去
mkdir $(test_dir)
cp test $(test_dir)
.PHONY : uninstall //运行:make clean 命令后,make会执行命令把变量test_dir指定的目录以及目录中的文件全部删除
uninstall :
rm -rf $(test_dir)
(3)文件查找
因为程序的源文件有可能放置在不同的子目录中,但是源文件被分散存储后,makefile提供了两种方法:VPATH,vpath.
VPATH:一个特殊的变量,make在当前路径下找不到源文件的话,就自动到VPATH中指定的路径中去寻找。
VPATH = 目录 : 目录.... //使用方法
eg: VPATH = /a : /b //make会在当前路径找不到文件时按照顺序依次查找/a和/b目录
vpath:与VPATH不同的是,vpath并不是变量而是关键字,其作用和VPATH类似,但使用方式更加灵活。
vpath 模式 目录 : 目录... //使用方法
eg:vpath %.c /a : /b
vpath %.c/a
vpath %.h /b
(4)嵌套执行
由于所有的源文件的编译规则命令过于庞大,如果写在一个makefile中,会造成makefile内容多。导致在进行修改和编译时因为人为因素产生很多错误。
所以提出了解决办法:把makefile分解成多个子Makefile文件,并放置到程序的每个子目录中,每个子目录负责所在目录下面的源文件的编译工作。
cd 子目录 && $(MAKE) //使用方法
$(MAKE) -c 子目录 //使用方法
//make会先去读取程序根目录下的makefile文件,然后再去读取各个目录中的子makefile文件。(这个过程就是make的嵌套执行)
(5)条件判断
与C语言的条件编译类似。条件判断的书写格式:
条件表达式 条件表达式
如果真执行的文本段 or 如果真执行的文本段
endif else
如果假执行的文本段
endif
条件表达式有四种格式:
1. ifeq(参数1,参数2) //比较参数1和参数2的值是否相同,相同为真,相异为假
2. ifneq(参数1,参数2) //相同为假,相异为真
3. ifdef(参数) //参数非空为真,空为假
4. ifndef(参数) //参数非空为假,空为真
(6)函数
这个作用不用多说了,和变量一样,函数也用符号
进
行
标
识
,
格
式
为
‘
进行标识,格式为`
进行标识,格式为‘(函数名 参数,参数…)`
三.Cmake
- 什么是Cmake?
Cmake就是一个工具,不过它有很多优点,如跨平台,能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。 - Cmake编译原理:
Cmake是根据CMakeLists.txt(组态栏)文件来用cmake命令将CmakeList.txt转化为make所需要的makefile文件,最后用make命令编译源码生成可执行程序或共享库(so(shared object))。 - CMake的编译基本就两个步骤:camke,make
cmake 指向CMakeLists.txt所在的目录,cmake后会生成很多编译的中间文件以及makefile文件,所以一般建议新建一个新的目录,专门用来编译。eg:cmake… 表示CMakeLists.txt在当前目录的上一级目录
make 根据生成makefile文件,编译程序。 - CMakeLists.txt的构成:External Cache Entries(外部缓存条目)和Internal Cache Entries(内部缓存条目)
而CMakeLists.txt是由解析器Parser生成,解析器的匹配器找到各种token。CMakeLists也可以解析外部的CMake语法,解析完这些变量,cmake在内存中有了项目(可执行程序、库、用户自定义Command)的构建表达方法。在代码中一个target用cmTarget对象表示,所有的cmTarget构成了cmMakefile对象。 - Cmake的依赖管理
CMake在使用IDE的平台不生成依赖,这些依赖由IDE(集成开发环境 )自己完成。 - CMakeLists.txt的编写
CMakeLists.txt命令对大小写不敏感,大小都OK,但是参数大小写要注意。#表示注释
eg:下面文件都是在src目录上,我们一般是在build目标下面运行cmake,这样是为了文件整理分类清晰,防止cmake运行附带的文件跟源代码混在一起,太杂乱了,当我们清理的时候不方便。
CMAKE_MINIMUM_REQUIRED(VESRSION 2.8) #cmake最低版本要求,低于2.8构建过程会被终止
PROJECT(xxxxxx) #定义工程名称
#MESSAGE(STATUS "Project : ${PROJECT_NAME}")
#MESSAGE(STATUS "Project Directory : ${PROJECT_SOURCE_DIR}") 打印相关信息
ADD_EXECUTABLE(main main.c) #表示最终要生成的可执行文件叫做main,使用的源文件是main.c
#add_executable(main main.c xxx.c x.c xx.c ...) 使用的源文件可以继续加
aux_source_directory(. SRC_LIST) #(.是在当前目录)同一目录下有多个源文件时候,使用这个命令
#作用:把当前目录下的源文件存列表存放到变量SRC_LIST里,然后在add_executable调用SRC_LIST
#eg: add_executable(main ${SRC_LIST})
#aux_source_directory()也存在弊端,它把指定目录下的所有源文件都加进来,会引入一些我们不需要的文件,这时我们可以用set命令去新建变量来存放需要的源文件
set(SRL_LIST
./main.c
./xxxx.c
./xx.c)
include_directories(目录 目录) #这运用于不同目录下多个源文件的情景下
#作用:向工程添加多个指定头文件的搜索路径,路径之间空格分离
#嵌套(在最外层新建一个CMakeLists.txt)
#把源文件放在src目录上,把头文件放进include文件,生成的对象文件放入build。
cmake_minimum_required(VERSION 3.1)
project(demo)
add_subdirectory(src)
#作用:可以向当前工程添加存放源文件的子目录,并可以在指定中间二进制和目标二进制的存放位置
#当执行cmake时,就会进入src目录下去找src目录下的CMakeLists.txt,所以在src目录下也建立一个CMakeLists.txt
aux_source_directory(. SRC_LIST)
include_directories(../include)
add_executable(main ${SRC_LIST})
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) #这的set命令是用于定义变量的
#EXECUTABLE_OUT_PATH,PROJECT_SOURCE_DIR(工程的根目录)是Cmake自带的预定义变量
#把源文件放在src目录上,把头文件放进include文件,生成的对象文件放入build。也可以写成一个文件
cmake_minimum_required(VERSION 3.1)
project(demo)
set (EXECUTABLE_OUTPUT_PATH &{PROJECT_SOURCE_DIR}/bin)
aux_source_directory(src SRC_LIST)
include_directories(include)
add_executable(main ${SRC_LIST})
add_library(xxxx_shared SHARED &{SRC_LIST})
add_library(xxxx_static STATIC ${SRC_LIst}) #作用:生成动态或者静态库
#add_library(第一个参数:指定库的名字 第二个:类型,动态库还是静态库 第三个:指定生成库的源文件)
set_target_properties(xxxx_shared PROPERTIES OUTPUT_NAME "XXXX")
set_target_properties(xxxx_static PROPERTIES OUTPUT_NAME "XXXX")
#设置最终生成库的名称,还有其它的功能,如设置库的版本号等等
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) #库文件输出路径的设置
find_library(TESTFUNC_LIB xxx目录 HINTS ${PROJECT_SOURCE_DIR}/xxx目录/lib)
# 在指定目录下查找指定库,并把库的绝对路径存放到变量里面
#第1个参数:变量名称 第2个:库名称 第3个:HINTS 第4个:路径
target_link_libraries(main ${XXX_LIB}) #把目标文件与库文件进行链接
add_compile_options(-std = c++20 -Wall) #编译程序时添加一些编译选项,如-Wall,-std = c++20 ......
SET(CMAKE_BUTLE_TYPE DUBEG) #指定编译类型debug版本