当我们在Makefile中重复写很多文件名时,一来容易写错,二来如果要改名,要全部替换,费时费力。

编程语言使用变量(Variable)来解决反复引用的问题,类似的,在Makefile中,也可以使用变量来解决重复问题。

以上一节的Makefile为例:

  1. world.out: hello.o main.o
  2. cc -o world.out hello.o main.o
  3. clean:
  4. rm -f *.o world.out

编译的最终文件world.out重复出现了3次,因此,完全可以定义一个变量来替换它:

  1. TARGET = world.out
  2. $(TARGET): hello.o main.o
  3. cc -o $(TARGET) hello.o main.o
  4. clean:
  5. rm -f *.o $(TARGET)

变量定义用变量名 = 值或者变量名 := 值,通常变量名全大写。引用变量用$(变量名),非常简单。

注意到hello.o main.o这个“列表”也重复了,我们也可以用变量来替换:

  1. OBJS = hello.o main.o
  2. TARGET = world.out
  3. $(TARGET): $(OBJS)
  4. cc -o $(TARGET) $(OBJS)
  5. clean:
  6. rm -f *.o $(TARGET)

如果有一种方式能让make自动生成hello.o main.o这个“列表”,就更好了。注意到每个.o文件是由对应的.c文件编译产生的,因此,可以让make先获取.c文件列表,再替换,得到.o文件列表:

  1. # $(wildcard *.c) 列出当前目录下的所有 .c 文件: hello.c main.c
  2. # 用函数 patsubst 进行模式替换得到: hello.o main.o
  3. OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
  4. TARGET = world.out
  5. $(TARGET): $(OBJS)
  6. cc -o $(TARGET) $(OBJS)
  7. clean:
  8. rm -f *.o $(TARGET)

这样,我们每添加一个.c文件,不需要修改Makefile,变量OBJS会自动更新。

思考:为什么我们不能直接定义OBJS = $(wildcard *.o)make列出所有.o文件?

内置变量

我们还可以用变量$(CC)替换命令cc

  1. $(TARGET): $(OBJS)
  2. $(CC) -o $(TARGET) $(OBJS)

没有定义变量CC也可以引用它,因为它是make的内置变量(Builtin Variables),表示C编译器的名字,默认值是cc,我们也可以修改它,例如使用交叉编译时,指定编译器:

  1. CC = riscv64-linux-gnu-gcc
  2. ...

自动变量

Makefile中,经常可以看到$@$<这样的变量,这种变量称为自动变量(Automatic Variable),它们在一个规则中自动指向某个值。

例如,$@表示目标文件,$^表示所有依赖文件,因此,我们可以这么写:

  1. world.out: hello.o main.o
  2. cc -o $@ $^

在没有歧义时可以写$@,也可以写$(@),有歧义时必须用括号,例如$(@D)

为了更好地调试,我们还可以把变量打印出来:

  1. world.out: hello.o main.o
  2. @echo '$$@ = $@' # 变量 $@ 表示target
  3. @echo '$$< = $<' # 变量 $< 表示第一个依赖项
  4. @echo '$$^ = $^' # 变量 $^ 表示所有依赖项
  5. cc -o $@ $^

执行结果输出如下:

  1. $@ = world.out
  2. $< = hello.o
  3. $^ = hello.o main.o
  4. cc -o world.out hello.o main.o

参考源码

可以从GitHub下载源码。

GitHub

小结

使用变量可以让Makefile更加容易维护。

查看官方手册: