在Makefile,有三个常用也很好用的自动化变量:$@、$^、$<,所谓自动化变量是在模式规则中定义的一系列文件自动挨个的去除,直至所有的符合模式的文件都取完。这么说可能比较绕,我们可以简单直白的理解,我们写的应用程序,稍微简单的也要有几个文件(.c、.h等),那么复杂的可能有几百,几千,甚至几十万(kernel),我们在编写Makefile时,如果逐个文件去编译,链接,那是绝不可能的,所以我们需要一种自动规则,一条命令就能干很多重复的事儿,在C编程里,我们有各种循环操作,比如for、while、do等等。那么自动化变量就是用于Makefile中能够自动循环执行命令的变量,而且这些自动化变量还特别“智能”,会自动识别、切换。
先简单的说明一下这3个自动化变量的含义:
$@
表示目标集,“集”的意思就是组合,全部,有多个目标,$@就是目标集合。
$^
所有依赖目标的集合,注意,这里说的是“依赖”,也就是目标的组成元素。
$<
依赖目标中的第一个目标名字,也就是上面说的$^中的第一个元素。
特别注意:我们可能会有一个疑惑,这个自动化变量的应用范围是什么?是整个Makefile吗?答案是:不是!
我简单的认为:自动化变量的应用范围是当前目标,Makefile中可以有很多的目标,但是终极目标只有1个,就是第一个,而其他的组成目标,也是需要编译的,所以我们在使用自动化变量时,它的应用范围只限于当前目标,这一点我们详细举例来说:
假设我们有一个应用工程,包含5个文件,分别为 main.c func1.c func1.h func2.c func2.h,代码如下:
/*------------------- main.c ---------------------*/
#include "func1.h"
#include "func2.h"
int main(int argc, char **arcv)
{
func1("hello");
func2("hello");
}
/*------------------- func1.c ---------------------*/
#include "func1.h"
#include <stdio.h>
void func1(char *print_str)
{
printf("this is func1 %s \n", print_str);
}
/*------------------- func1.h ---------------------*/
void func1(char *print_str);
/*------------------- func2.c ---------------------*/
#include "func2.h"
#include <stdio.h>
void func1(char *print_str)
{
printf("this is func2 %s \n", print_str);
}
/*------------------- func2.h ---------------------*/
void func2(char *print_str);
Makefile编写如下:
main : main.o func1.o func2.o
gcc main.o func1.o func2.o -o main
main.o : main.c func1.h func2.h
gcc -c main.c
func1.o : func1.c func1.h
gcc -c func1.c
func2.o : func2.c func2.h
gcc -c func2.c
.PHONY : clean
clean :
rm *.o
这么编写是比较中规中矩的,如果考虑到Makefile的隐晦规则(潜规则),只需要前两行就行了,因为make会自动推导。
如果文件不是5个,而是50、500个或者更多,我们很显然不能这么干,这个时候就需要自动化变量了,我们可以使用这3个自动化变量先把上面的Makefile初步简化一下,如下所示:
main : main.o func1.o func2.o
gcc $^ -o $@
main.o : main.c func1.h func2.h
gcc -c $<
func1.o : func1.c func1.h
gcc -c $<
func2.o : func2.c func2.h
gcc -c $<
.PHONY : clean
clean :
rm *.o main
看到这里,可能会疑惑,这叫哪门子简化,不就是符号替换吗,别着急,上面的代码只是为了说明这3个自动化变量代表什么,因为能替代,恰恰就说明了他们的作用,这里再重复,分析下:
$@ —— 目标集,首先必须是“目标”,其次这是个集,可以是1个,也可以是多个。
$^—— 所有的依赖对象集,如果觉得拗口,可以理解为,所有的组成元素,这是个集,可以是1个,也可以是多个。
$< —— 依赖对象集中的第一个,这里要说明一下,gcc只能编译*.c和.S文件,不能编译*.h头文件的,所以上面的
gcc -c $< 表示的是 gcc -c *.c,如果我们写成 gcc -c $^,会报错,因为这个展开来变成了gcc -c *.c *.h
从这里也可以验证之前说的话,自动化变量很智能,其应用范围仅限于当前目标,不是全范围,如果是全范围就乱套了。所以每个目标都可以使用这些自动化变量。
那么我们进一步简化Makefile,如下:
main : main.o func1.o func2.o
gcc $^ -o $@
.c : .o
gcc -c $<
.PHONY : clean
clean :
rm *.o main
第一个目标的命令中$^代表所有依赖元素集,也就是main.o func1.o func2.o,$@代表最终的目标集,这里只有一个:main
.c : .o命令是Makefile中 静态模式的用法,这个名字我觉得翻译的不够形象,还不如叫自动循环命令呢,这里简单的说下,就是告诉make,所有的.o文件有对应名字的.c文件编译获得,所以下面的命令中 $<表示第一个依赖元素,到这里才会豁然开朗,
.c:.o会执行很多次(取决于有多少个.c文件),每一次执行,“$<”都代表第一个元素,也就是所谓的.c文件。非常灵活、智能。
所以这条命令还可以修改如下:
.o : .c
gcc -c $< -o $@
看到了把,原来是.c : .o,现在反过来了,变成了 .o : .c,但是对应命令也变了,展开来就是 gcc -c xx.c -o xx.o,这条命令的逻辑仍然是对的,到这里应该就能理解自动化变量的“自动”二字了,所谓自动,就是智能,管你写的什么顺序,我万变不离其宗,只按照它的理解去做,只去第一个($<)、所有依赖元素($^)、所有目标集($@),而且应用范围仅限于当前命令行对应的目标依赖关系,下一个目标会再智能转换。