1、使用makefile的原因是因为,当工程很大的时候,有很多文件,这些文件也有可能很分散,若直接用gcc编译则拼写gcc命令很长,很麻烦,很容易出错,所以借助makefile工具将所有编译命令写到文件中帮助gcc编译,避免出错,方便管理。
2、makefile的命名只有两种方式makefile和Makefile
3、规则中的三要素:目标,依赖,命令。
目标:依赖文件
tab键后跟命令
举例子在main函数中依次会调用到add.c min.c mul.c 和div.c中的函数,我们期待生成一个App,写vi makefile文件中写makefile版本1如下内容
App:main.c add.c min.c mul.c div.c
gcc main.c add.c min.c mul.c div.c -o App
保存makefile内容退出后,在终端输入make指令即可编译执行。
4、make指令需要预先安装,安装指令为sudo apt install make
5、gcc main.c add.c min.c mul.c div.c -o App这句话的缺点是每个文件都编译了一遍,假如我们只是修改了其中某一个文件的话,其实只用编译这个修改的文件即可。在有几百个文件的系统中,每次只编译更改的文件而不是全部编译这样效率更高。因此修改makefile版本2如下所示,
App:main.o add.o min.o mul.o div.o
gcc main.o add.o min.o mul.o div.o -o App
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
min.o:min.c
gcc -c min.c
mul.o:mul.c
gcc -c mul.c
div.o:div.c
gcc -c div.c
以上的makefile中,第一行是终极目标(终极目标一定要写在最前边),当生成终极目标App时候发现没有main.o 然后就会往后边找,后边找到并生成main.o后,发现还缺add.o,继续再往后找,直到终极目标完成。当我们生成App可执行文件后,若发现第二次只修改add.c一个文件时候,此时make就只编译add.c一个文件。
makefile根据终极目标从上至下递归寻找依赖,然后从下至上执行命令直到生成终极目标。
6、makefile的工作原理:makefile检查文件是否有更新,makefile看目标文件和依赖文件的最后更新时间,若依赖文件的时间更接近现在的时间,那么就证明依赖文件最近被修改过,需要通过执行命令编译新的依赖文件来更新目标文件了。
7、makefile中的文件名字出现很多次,可以使用一个变量表示它们并替换它们,方便书写。
这里我们定义变量obj即 obj=main.o add.o min.o mul.o div.o然后用$符取obj中的值,$符的意思就是取变量中的值。例子如下
obj=main.o add.o min.o mul.o div.o
target=App
$(target):$(obj)
gcc $(obj) -o $(target)
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
min.o:min.c
gcc -c min.c
mul.o:mul.c
gcc -c mul.c
div.o:div.c
gcc -c div.c
8、对于很多行的的操作都一样,如对main.c add.c mul.c等的操作一样,只有名称不一样,我们可以使用模式规则来简化
使用模式规则之前
main.o:main.c
gcc -c main.c
使用模式规则之后
%.o:%.c
gcc $< -o $@
在这里当生成终极目标App时候需要检查依赖,当发现没有main.o ,就会向下寻找,找到这里就会匹配%.o为main.o,由于%的内容一样的所以%c就为mainc,整行就为main.o:main.c。然后再依次查找add.o、min.o、mul.o、div.o并执行相应的匹配。
$<和$@及$^都是makefile中的自动变量,自动变量就是指它的值是不定的。这三个自动变量只能在规则中使用。
$<表示当前规则中的第一个依赖,这里是指main.c
$@表示当前规则中的目标, 这里是指main.o
$^表示当前规则中所有的依赖,假如是终极目标那一行的所有依赖项这里是指main.o、add.o、min.o、mul.o、div.o
综合以上我们修改makefile版本3如下
obj=main.o add.o min.o mul.o div.o
target = App
$(target):$(obj)
gcc $(obj) -o App
%.o:%.c
gcc -c $< -o $@
9、makefile系统中还有一些系统维护的变量,这些系统变量都是大写的。系统维护的变量有些有默认值,有些没有默认值,无论有无默认值我们都可以修改。
CC的默认值为cc即CC=cc(cc功能其实gcc)我么可以改为CC=gcc CPPFLAGS是预处理时候选项可以设置为-I即CPPFLAGS=-I此外还有编译时候参数CFLAGS和链接时候参数-Wall -g -c ,LDFLAGS链接库使用的选项 -L -l
10、makefile中所有的函数都有返回值,不使用返回值话,没必要使用makefile中的函数。
11、当终极目标有很多依赖.o文件的时候,把所有.o文件写出来比较低效,且易出错。我们要生成终极目标需要所有.o文件,我们生成.o文件需要所有的.c,因此我们自下往上执行先找到.c文件,然后生成对应的.o文件,最后生成终极目标。我们可以借助makefile中wildcard函数获取指定目录下的.c文件
src= $(wildcard ./*.c)
wildcard是函数名,是找到所有的.c文件的意思 ./*.c是函数参数 ,$(wildcard)是取函数wildcardd的返回值, wildcard函数查找某个目录下某种类型的文件,查到后以字符串形式返回,这里返回的是main.c add.c min.c mul.c div.c
obj= $(patsubst ./%.c, ./%.o, $(src))
patsubst是函数名, 是匹配替换的意思,./%.o, ./%.c, $(src)是指将当前文件下.c文件替换为.o文件, .c文件来源为$(src),然后obj就是main.o add.o min.o mul.o div.o
综合以上我们修改makefile版本4如下
src= $(wildcard ./*.c)
obj= $(patsubst ./%.o, ./%.c, $(src))
target = App
CC=gcc
$(target):$(obj)
$(CC) $(obj) -o App
%.o:%.c
gcc -c $< -o $@
12、有些时候我们每次需要删除.o文件,我们可以将这个操作写入makefile中, 一下clean这个目标没有依赖项,直接执行rm $(obj)操作
clean:
rm $(obj) $(target)
在makefile中,由于终极目标是App,并且终极目标与clean这个目标无关,所以在终端黑窗口敲make并不会执行clean相关操作。
要想执行clean操作,需要在终端敲make clean指令,不过make clean这个指令只会执行生成目标clean的rm操作,不会执行其他操作如生成终极目标的操作。
有些时候当clean进行rm删除操作的时候,当没有对应文件会告警,我们可以加-f参数要求强制执行,不管文件存不存在这个指令都会执行。
clean:
rm $(obj) $(target) -f
13、有些时候我们当前文件下已经有一个clean文件,如我们touch clean 后,再然后执行make clean会发现提示当前的clean是最新的,没有执行相关操作。 这是由于makefile的工作原理是当发现目标是最新的时候就不更新或执行相应操作。我们为此可以添加一个伪目标,声明clean为伪目标,就不会再做比较哪个clean更新了,不会和本地磁盘的clean比较了。
.PHONY:clean声明clean为伪目标。
综合以上我们修改makefile版本5如下
src= $(wildcard ./*.c)
obj= $(patsubst ./%.c, ./%.o, $(src))
target = App
CC=gcc
$(target):$(obj)
$(CC) $(obj) -o App
%.o:%.c
gcc -c $< -o $@
.PHONY:clean
clean:
rm $(obj) $(target) -f
14、特别注意有些时候会在指令前边加一个横杠-,表示的是当前命令执行失败的话,忽略错误继续往下执行。
clean:
-rm $(obj) $(target) -f
综合以上我们经常写的简单易懂makefile易读版本如下
App:main.o add.o min.o mul.o div.o
gcc main.o add.o min.o mul.o div.o -o App
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
min.o:min.c
gcc -c min.c
mul.o:mul.c
gcc -c mul.c
div.o:div.c
gcc -c div.c
.PHONY:clean
clean:
-rm *.o App -f