1.Makefile作用
作用:组织管理程序。
为什么需要Makefile怎么高效地编译程序想达到什么样的效果?请参考 Visual Studio:修改源文件或头文件,只需要重新编译牵涉到的文件,就可以重新生成 APP。
我们使用Makefile可以为我们工程提供一套编译系统,也可以非常简单的去执行某些命令,就可以去编译文件,可以去清除文件。
2.Makefile的内部机制
Makefile的内部机制:预处理,编译,汇编,链接。
对于命令:
gcc -o test a.c b.c [-v]
a.c,b.c预处理编译为xxx.s,再汇编成xxx.o,最后一起链接成test.
该命令十分简单,但是有缺点,就是每执行一次,它就会对a.c和b.c依次进行预处理 编译 汇编这三个过程,最后再把它们链接在一起。(当我只修改a.c,b.c没有进行修改,执行该命令,它不仅仅对修改的a.c进行处理,也对没修改的b.c进行了处理,实际上没有必要)。
进行改进,应该先分别编译,最后在链接。
gcc -c -o a.o a.c //编译a.c为a.o
gcc -c -o b.o b.c //编译b.c为b.o
gcc -o test a.o b.o //链接a.o,b.o为test
当我们只修改a.c后,我们可以只进行第一步和第三步,消去了对b.c的编译。
对于上面的命令,有个问题,那就是电脑如何判断哪些文件被修改了呢?
解决:比较时间
即:比较a.o与a.c的时间,比较b.o与b.c的时间,比较test与a.o,b.o的时间.
3 Makefile的规则
3.1 规则:是Makefile最根本元素
目标文件(target):依赖文件(prerequiries) 命令 |
注:
(1)目标(target)通常是要生成的文件的名称,可以是可执行文件或OBJ文件, 也可以是一个执行的动作名称,诸如`clean’。
(2)依赖是用来产生目标的材料(比如源文件),一个目标经常有几个依赖。
(3)当“依赖文件”比“目标文件”新 时 或目标文件不存在时,执行命令。
3.2 将Makefile内部机制改写成Makefile文件
test : a.o b.o
gcc -o test a.o b.o
a.o : a.c
gcc -c -o a.o a.c
b.o :b.c
gcc -c -o b.o b.c
Makefile文件就是名为Makefile的文件里放着这些内容,当我们像编译程序时,执行“make”即可
实际中该命令的执行情况
(1)执行“make”,要生成test,需要a.o,b.o,然后它发现没有a.o,b.o,它发现a.c比a.o新,会执行gcc -c -o a.o a.c命令,也发现b.c比b.o新,会执行gcc -c -o b.o b.c命令,然后生成test
(2)当修改a.c后,执行“make”,要生成test,要a.o,b.o,它发现,a.o依赖a.c,b.o依赖b.c,它发现a.c比a.o新,会执行gcc -c -o a.o a.c命令,最后因为新的a.o与旧的b.o其中有一个比test新,所以执行gcc -o test a.o b.o,生成test。
4 Makefile的语法
4.1 通配符:“ %.o ”
对于上面Makefile中的命令
test : a.o b.o
gcc -o test a.o b.o
a.o : a.c
gcc -c -o a.o a.c
b.o :b.c
gcc -c -o b.o b.c
有一点缺点:就是,因为test依赖只有a.o,b.o,所以表面上看起来还可以,但是如果依赖有100个,1000个,那么就很麻烦了。所以我们引入了通配符。
对于上面的命令修改成
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
注:
(1)所以的依赖用” $^ “表示。
(2)不可写成gcc -c -o %.o %.c。其中“ %.o ”用” $@ “代替,” %.c “用” $< “代替
总结:
$@ | 目标文件 |
$< | 依赖文件 |
$^ | 所有依赖文件 |
4.2 即时变量,延时变量,expert
变量使用时格式为:
$(变量)
即时变量(简单变量)
A:=xxx #A的值即刻被锁定
延时变量
B=xxx #B的值使用时才确定
示例:
A := $(c)
B = $(c)
C = abc
all:
@echo A=$(A)
@echo B=(B)
结果: A=
B=abc
结果解析:
(1)因为A为即时变量,在定义时马上确定A的值,但是因为C为即时变量,而且在A后面定义,所有在确定A的值时,C还没被F值,所以A的值为空。
(2)因为B为延时变量,在使用到时才确定值,即echo B=$(B)时。所以B的值为abc。
补充1:
A := $(c)
B = $(c)
all:
@echo A=$(A)
@echo B=(B)
C = abc
当把C=abc放在@echo B=$(B)后面时,B的值也是abc,所以C=abc的位置对B的值无影响。
补充2:
A := $(c)
B = $(c)
C = abc
all:
@echo A=$(A)
@echo B=(B)
C = 123
结果: A=
B=123
结论:C原来的”abc“被”123“覆盖
补充3:
A := $(c)
B = $(c)
C = abc
all:
@echo A=$(A)
@echo B=(B)
C += 123
结果: A=
B=abc123
总结:
:= | #即时变量 |
= | #延时变量 |
?= | #延时变量,如果是第一次定义时才起效,如果前面变量已定义,则忽略这句 |
+= | #附加,它是延时还是即时取决于前面的定义 |
4.3 假想目标
4.3.1 前言:如果我们”清除文件“怎么办?
1.如果使用命令:
rm *.o test
解释:“ *.o ”表示所有的.o文件。" test "代表test文件。
2.如果使用Makefile
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm *.o test
调用“ make clean”执行“ rm *.o test ”清除所有文件
注:
(1)通过“ make clean ”命令可以猜到,make后面带目标名,也可以不带。
(2)如果不带,它就会去生成第一个规则里面的第一个目标。
总结:
使用Makefile
make [目标]
若无目标,默认第一个目标。
4.3.2 假想目标出现的原因
因为有clean命令,但是如果Makefile编译的文件中有同名clean的文件时,执行“ make clean”时,会出现:’clean‘ is up to data.
问题解析:它说:clean文件已经更新,它根本没有执行“ clean ”删除命令。
出现问题解析:原因,一个规则可以执行的条件:“目标文件不存在”或“依赖比目标新”,现在我有名为“clean”的目标文件,但是它根本没有依赖文件,所以依赖不会比目标新。同时有同名的clean文件,导致目标文件存在,所以根本无法执行操作。
所以问题是:如果有同名的clean文件时,就无法执行" make clean "命令
解决方案:把”clean“目标定义为假想目标 : ".PHONY : clean"
即
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm *.o test
.PHONY : clean
5 Makefile函数
5.1 $(foreach Var , list , text)
对list中每一个元素,取出来赋值给Var,然后把Var改为test所描述的形式。
示例:
A = a b c
B = $(foreach f , A , $(f).o)
all:
echo B = $(B)
执行make:
结果:B = a.o b.o c.o
5.2 $(filter pattern... , text) 和 $(filter-out pattern... , text)
$(filter pattern... , text) #在text中取出符合pattern格式的值。
$(filter-out pattern... , text) #在text中取出不符合pattern的值
示例:
C = a b c d/
D = $(filter %/ , $(C)) #在C中寻找目录
E = $(filter-out %/ , $(C)) #在C中寻找非目录的东西
all:
@echo D = $(D)
@echo E = $(E)
注:a,b,c为文件,d/为目录
执行:make
结果:D = d/
E = a b c
5.3 $(wildcard pattern)
#pattern定义了文件名的格式
#wildcard取出其中存在的文件
示例1:
假设现有目录为:
a.c b.c c.c Makefile |
Makefile中:
files = $(wildcard *.c)
all:
@echo files = $(files)
执行make:
结果:
files = a.c b.c c.c
示例2:
假设现有目录为:
a.c b.c c.c Makefile |
Makefile中:
files2 = a.c b.c c.c b.c e.c
files3 = $(wildcard $(files2))
all:
echo file3 = $(files3)
执行make:
结果:files3 = a.c b.c c.c
结果分析:因为在目录中实际存在的文件为a.c,b.c,c.c,该函数可以找出真实存在的文件,所以d.c,e.c不会被找出。
5.4 $(patsubst pattern , replcement , $(Var))
#把Var中的文件取出来,把pattern替换为replcement。
示例:
files2 = a.c b.c c.c e.c abc
dep_files = $(patsubst %.c , %.d , $(files2r))
all:
@echo dep_files = $(dep_files)
执行make:
结果:dep_file = a.d b.d c.d d.d e.d abc
6 Makefile改进
6.1 支持头文件依赖
如果存在c.c和c.h函数
c.c中:
#include <stdio.h>
#include "c.h"
void func_c()
{
printf("this is C = %d\n",C);
}
在c.h中:
define C 1
Makefile中:
test : a.o b.o c.o
gcc -o test a.o b.o c.o
%.o : %.c
gcc -c -o $@ $<
clean :
rm *.o test
.PHONY : clean
(1)执行make编译。
(2)执行 ./test 运行代码,成功 " C = 1 "。
(3)执行" vi c.h ",编辑c.h文件,改成" define C 2"
(4)执行make ,结果“make:'test' is up to data"
(5)执行 ./test 运行代码 “ C = 1”.
思考:为什么明明已经修改了c.h,运行 ./test 结果还是和原来一样
原因:在Makefile中,%.o依赖%.c,当我们修改c.h时,%.o不会更新,所以test也不会变,所以运行结果跟之前一样。
解决1:
Makefile中添加
c.o : c.c c.h
缺点:我们不容易确定一个文件有多少个.c和.h文件,手动写出不现实,我们需要自动来生成这些规则。
解决2:
gcc -M c.c //打印出依赖
gcc -M -MF c.d c.c //把依赖写入c.d
gcc -c -o c.o c.c -MD -MF c.d //编译c.o,把依赖写入文件c.d
修改Makjefile
test : a.o b.o c.o
gcc -o test a.o b.o c.o
%.o : %.c
gcc -c -o $@ $< -MD -MF .$@.d
执行make:
执行 ls -a
a.c a.o .a.o.d
b.c b.o .b.o.c
c.c c.d c.h clean c.o .c.o.d
Makefile
注:得到.%.o.d文件后,要判断是否为空,不为空才能包含依赖。
再次修改Makefile文件:
objs = a.o b.o c.o
dep_files := $(patsubst % , .%.d , $(objs)) #所以文件加上前缀. 和后缀 .d
dep_files := $(wildcard , $(dep_files)) #判断生成的文件是否真实存在
test : $(objs)
gcc -o test $^
ifneq ($(dep_files) , ) #判断dep_files不为空
include $(dep_files) #将dep_files文件包含进来
endif
%.o : %.c
gcc -c -o $@ $< -MD -MF .$@.d
clean :
rm *.o test
distclean :
rm $(dep_files) #删除所有文件
.PHONY : clean
6.2添加CFLAGS
CFLAGS编译参数
示例1:
CFLAGS = -Werror #把所有warming变为error
注:gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d
示例2:
CFLAGS = -I. #指定头文件为当前目录
CFLAGS = -Iinclude #指定头文件目录为include目录