前面我们介绍了Makefile运行规则,这一章我们介绍Makefile的语法。
1. 通配符
什么是通配符?学过其他编程语言的可能只要什么叫通配符,通配符是一种特殊语句,主要有星号(*)和问号(?),用来模糊搜索文件。当查找文件夹时,可以使用它来代替一个或多个真正字符。
1.1 Makefile常见通配符
Makefile具有较多通配符,但是常见的也就几种。如下所示:
- %.o或%.c : 该通配符表示任意的.o文件或者.c文件
- $@ : 表示目标
- $< : 表示第一个依赖文件
- $^ : 表示所有依赖文件
1.2 Makefile常见通配符实验
以下为上节课我们使用的Makefile文件
test : main.o sub.o
gcc -o test main.o sub.o
main.o : main.c
gcc -c -o main.o main.c
sub.o : sub.c
gcc -c -o sub.o sub.c
我们将使用通配符进行改进,实现我们使用%.o和%.c文件进行改进,
test : main.o sub.o
gcc -o test main.o sub.o
%.o : %.c
gcc -c -o main.o main.c
运行逻辑:
- 当一开始运行程序时,没有main.o,那么会向下查找,发现通配符%.o, 那么此时将%替换为main,执行: gcc -c -o main.o main.c 命令
- 生成main.o后,再次查找依赖sub.o, 发现没有改文件,那么需要往下查找,此时将%替换为sub, 执行gcc -c -o main.o main.c命令,不过意外的是,这里还是生成main.o, 最后还会编译不了 test, 系统报错,相应过程见下图。
需要对上述Makefile进行修改,使用
test : main.o sub.o
gcc -o test main.o sub.o
%.o : %.c
gcc -c -o $@ $<
$@表示目标 , $<表示第一个依赖文件, 那么当%为main时,此时 $@ 表示main.o, $<表示main.c。当%为sub时,此时 $@ 表示sub.o, $<表示sub.c。 具体运行过程见下图:
这样就能正常运行了, 不过是否考虑到一个问题,如果有很多OBJ文件呢? 难道我们需要一个一个加上XX.o吗,那么此时gcc -o test main.o sub.o这条语句得使劲加?显然这样是不智能的,我们可以使用$^ : 通配符,表示所有依赖文件,改进的程序如下:
test : main.o sub.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
当运行 gcc -o test $^ 命令时,程序会将 $^ 替换为main.o sub.o, 这样的话我们有多个源文件,只需要往sub.o后添加就可以了。下图为运行过程图:
2. 假想目标
我们先不提什么是假想目标,我们慢慢引入。
2.1 清除中间文件
我们希望删除中间文件(.o文件)和目标文件(test), 以确保我们将编译过程产生的文件全部删除,我们可以在Makefile文件中加入 rm *.o test命令,其中 *.o表示全部的OBJ文件,test为可执行程序。那这条语句应该怎么加入到Makefile文件中呢?我们可以仿造一下,直接写clean: (不需要依赖文件)。当我们输入make时(不带任何参数),此时系统会找到文件的第一个目标文件(第一行的开头的目标:test), 默认执行make test, 此时系统运行第一行,在清楚时,输入make clean, 系统会跳转到clean处,由于不需要依赖文件,所以任何时间都会运行,此时执行rm *.o test 以下为所有代码
test : main.o sub.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
clean:
rm *.o test
下图为实验过程:
2.2 本地存在clean文件
是否想过一个问题,如果在当前目录存在clean文件,那么当我们运行make clean时会出现什么情况?
如上图所示:我们使用 > clean命令创建一个空的clean文件,然后再执行make clean, 此时发现显示:make: ‘clean’ is up to date. 这个报错表示clean文件是最新的, 不需要再执行了,这样就不能执行make clean操作了,此时我们可以使用.PHONY: clean 设置clean为假想目标,详细代码如下:
test : main.o sub.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
clean:
rm *.o test
.PHONY: clean
下图为详细的试验过程
3. 变量
3.1 分类及定义
Makefile的变量分为即时变量(也称简单变量)、延时变量,即时变量是在定义时就确定值,而延时变量是在使用时才确定的值,在没使用 时一直为空,以下为使用举例:
- A := xxx # A的值即刻确定,在定义时即确定
- B = xxx # B的值使用到时才确定
3.2 实验
3.2.1 使用变量
Makefile中使用变量使用$(变量名称),代码中的all和之前讲的clean类似, 由于代码中只有一个目标文件all,所以在运行时输入make和make all的效果是一样的,echo是Makefile中的打印函数,测试代码如下:
A := abc
B = 123
all:
echo $(A)
echo $(B)
运行结果:
上述图中echo abc和echo 123是打印命令,如果不想打印,可以在echo前面加入@,代码如下:
A := abc
B = 123
all:
@echo $(A)
@echo $(B)
运行结果:
此时我们还是看不出即时变量和延时变量的区别,接着我们引入一个新变量C,代码如下:
A := $(C)
B = $(C)
C = abc
all:
@echo $(A)
@echo $(B)
运行结果:
有上图可知,A变量值为空, B变量的值为abc。
运行机制如下:运行A := $©时,由于C变量未定义,而且A为即时变量,此时A的值已经确定(为空),运行B = $©时,即使C变量未定义,但是B为延时变量,在引用时才确定值,运动C = abc,定义了延时变量C,运行@echo $(A)直接打印A的值(空),运行@echo $(B)此时先引用B,在引用时C变量已经定义,有值(abc),故此时B的值也确定了(也为abc),故打印abc。
为了更加清晰的看出变量的值,我们加上辅助信息,代码如下:
A := $(C)
B = $(C)
C = abc
all:
@echo A = $(A)
@echo B = $(B)
运行结果:
有人会想是否和C = abc的位置有关呢?我们修改代码如下:
A := $(C)
B = $(C)
# C = abc
all:
@echo A = $(A)
@echo B = $(B)
C = abc
运行结果:
由运行结果可知,可位置是没关系的,是因为Makefile文件在运行前会将所有代码读入,当运行make或make all时才生产目标文件。那么我们是否会考虑代码加载的顺序?是从上到下加载吗?我们不妨做个实验:
A := $(C)
B = $(C)
C = abc
all:
@echo A = $(A)
@echo B = $(B)
C = 123
观察以上代码,如果打印B=123,表示是顺序执行的,并且后面的值覆盖了前面的值
运行结果:
从上图结果可以看出,的确和我们的猜想是一样的。
3.3 变量运算及赋值方式
3.3.1 常见的赋值方式
- ?= : 也是延时变量赋值的一种,区别在于如果是第一次定义才起效,如果在前面该变量已定义则忽略这句。
- +=:附加,它是即时变量还是延时变量取决于前面的定义
3.3.2 赋值方式实验
我们做 += 的实验,运行如下代码:
A := $(C)
B = $(C)
C = abc
all:
@echo A = $(A)
@echo B = $(B)
C += 123
运行结果:
由结果可知,C还是为即时变量,因为之前是采用 =进行定义的。
我们再做 ?= 的实验,运行如下代码:
A := $(C)
B = $(C)
C = abc
D = LingTu
D ?= LLLS
all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)
C += 123
运行结果:
由上图可知,D = LingTu在D ?= LLLS之前定义了, 故D ?= LLLS定义不起效果。
我们再做一个对比,代码如下:
A := $(C)
B = $(C)
C = abc
D ?= LLLS
all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)
C += 123
运行结果:
此时D ?= LLLS为第一次定义变量,故起效果。
除了在代码中幅值外,我们也可以在输入命令时赋值,命令行的赋值先于代码中的赋值,实验如下:
注意命名行赋值时参数不要输入空格,命令行是默认以空格间隔参数的,第一次加入空格报错了。