make工具
当源码文件比较多的时候,不是适合直接输入gcc命令来编译,这时候就需要一个自动化的编译工具。make工具通过Makefile的文件来完成并自动维护编译。
当我们有多个源文件的是时候,我们只用GCC的话就会比较麻烦如:
gcc main.c input.c output.c -o main
那么这样的话生成的可执行文件,是可以运行的,但是如果我们有几千个源文件的话,工作量就非常大。并且,每次使用gcc进行编译,所有源代码都会重新编译,若我们文件较大,那么我们就每次修改都需要等待非常长的时间。
而make就解决了这个文件,综上Makefile可以满足一下几个需求:
- 如果工程没有编译过,那么工程中的所有.c文件都要被编译并且链接成可执行程序。
- 如果工程中只有个别C文件被修改,那么只编译这些被修改的C文件即可。
- 如果工程的头文件被修改了,那么我们需要编译所有引用这个头文件的C文件,并链接成可执行文件。
创建Makefile
在工程目录下创建Makefile的文件,文件名一定是Makefile。Makefile编写中所有的首行缩进都是用TAB。我们在新创建的Makefile中编写命令,如果我们需要运行Make的时候,可直接使用“make”命令编译工程。
出错原因:我们编写的Makefile可能会出错一般有两种原因:
- Makefile中命令缩进没有使用TAB键
- VI/VIM编译器使用空格代替了TAB键
Makefile语法
Makefile规则格式
Makefile里面是由一系列的规则组成的,这些规则格式如下:
目标```: 依赖文件集合```
命令 1
命令 2
`````
命令列表中的每条命令必须以 TAB 键开始,不能使用空格!
make命令会为Makefile中的每个以TAB开始的命令创建一个shell进程去执行。
拿出正点原子的官方给的示例来分析一下(我这里无法用TAB缩进我就用空格先代替)
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main.o: main.c
gcc -c main.c
input.o: input.c
gcc -c input.c
calcu.o: calcu.c
gcc -c calcu.c
clean:
rm *.o
rm main
根据上面所述,这个命令一共包括五条规则,1-2为第一条、3-4为第二条、5-6为第三条、7-8为第四条、10-12为第五条。make命令在执行上面Makefile的时候其执行步骤如下:
如果你觉得自己理解了,就跳过下面这段长长的我复制来的分析。
首先更新第一条规则中的 main,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为 Makefile 的工作。在第一次编译的时候由于 main 还不存在,因此第一条规则会执行,第一条规则依赖于文件 main.o、input.o 和 calcu.o 这个三个.o 文件,这三个.o 文件目前还都没有,因此必须先更新这三个文件。make 会查找以这三个.o 文件为目标的规则并执行。以 main.o为例,发现更新 main.o 的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc–c main.c”,这行命令很熟悉了吧,就是不链接编译 main.c,生成 main.o,其它两个.o 文件同理。最后一个规则目标是 clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行 clean 的话可以直接使用命令“make clean”,执行以后就会删除当前目录下所有的.o 文件以及 main,因此 clean 的功能就是完成工程的清理,“make clean”的执行过程。
总结一下就额是:
- make命令会在当前目录下查找以Makefile命名的文件
- 当找到Makefile文件以后就会按照Makefile中定义的规则来编译生成最终的目标文件
- 当发现目标文件不存在,或者目标文件所依赖的文件比目标文件新的话就会执行后面的命令来更新目标
这就是make的执行过程,make工具就是在Makefile中一层层的查找依赖关系,并执行相应的命令。编译出最终的可执行文件。
Makefile变量
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
上述命令中,我们同一个.o文件出现了两次,如果我们的Makefile文件比较复杂的时候重复输入的工作就会非常浪费时间,并且容易输错,为了解决这个问题,Makefile加入了变量支持。
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
第一行我们定义了一个变量objects,并且给这个变量进行了赋值,其值为字符串“main.o input.o calcu.o”;第三和第四行使用到了变量objects,Makefile中变量的引用方法是“$(变量名)”。
包括三中赋值方式“=”、“:=”、“”
- “=”的值取决于=右边所引用的变量的最后一次的有效值
- “:=”的值取决于引用变量之前的最后一次的有效值
- “?=” 如果变量没有被赋值,那么此变量就是右边引用的变量;如果已经赋值就使用当前值
- “+=“在原来的基础上增加
Makefile模式规则
模式规则用处:当多个类似处理的规则时,重复的使用会增加工作量,所以引用模式规则,通过模式规则我们就可以使用一条规则来将所有的.c文件编译为对应的.o文件。
模式规则中,至少在规则的目标定定义中要包含”%“,否则就是一般规则,目标中的”%“表示对文件名的匹配,”%”表示长度任意的非空字符串,例如“%.c”表示所有以.c结尾的文件。
Makefile自动化变量
如果通过一行命令来从不同的依赖文件中生成对应的目标呢?自动变量就可以解决这个问题。
自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个去除,直至所有的符合模式的文件都去玩,自动化变量只应该出现在规则的命令中。常用的三种:$@、$<和$^【Makefile中3个常用自动化变量示例】
Makefile条件判断
C语言中我们通过条件判断语句来根据不同的情况来执行不通过的分支,Makefile也一样支持条件判断。
格式:
<条件关键字>
<条件为真时执行的语句>
else
<条件为假时执行的语句>
endif
条件关键字有四个,ifeq、ifneq、ifdef、ifndef,比较熟悉的能猜到,相同、不同、非空、空。
Makefile函数调用
Makefile同样支持函数,Makefile中的函数是已经定义好的,我们直接使用,不支持我们用户自定义函数。
函数的使用方式:
$(函数名 参数集合)
${函数名 参数集合}
可以看出,调用函数和调用普通变量一样,使用符合“$”来标识。参数之间用“,”隔开。
subst函数
subst用于字符串替换,调用方式:
$(subst <form>,<to>,<text>)
将<text>中的<form>替换成<to>。
patsubst函数
patsubst用来完成模式字符串替换,调用方式:
$(patsubst <pattern>,<replacement>,<text>)
此函数查找字符串<text>中的单词是否符合模式<pattern>,如果匹配就是用<replacement>来替换掉,<parren>可以使用通配符“%”,表示任意长度的字符串,函数返回值就是替换后的字符串。
dir函数
dir函数用来获取目录,调用方式:
$(dir <names...>)
此函数用来从文件名序列<names>中提取出目录部分,返回值就是文件序列<names>的目录部分。
notdir函数
notdir函数就是去除文件中目录的部分,调用方式与dir相同。
foreach函数
foreach函数用来完成循环,调用方式:
$(foreach <var>,<list>,<text>)
此函数就是把参数<list>中的每一个单词逐一取出来放到参数<var>中,然后再执行<text>所包含的表达式。
wildcard函数
通配符“%d”只能用在规则中,只有在规则中它才能展开,如果变量定义和函数使用中,通配符不会自动展开,这个时候就需要使用函数wildcard函数,调用方法:
$(wildcard PATTERN)
$(wildcard *.c)
上面的代码就是获取当前目录下所有的.c文件,类似规则中的“%.c”。
总结
Make部分其实含有很多很多,我这只是列举一些学习中常用的部分,如果感兴趣可以去深入了解一些Makefile文档。