make 的隐式规则(十一)

        如果我们将同一个目标的命令拆分的写到不同地方,会发生什么呢?我们来看看下面的代码

.PHONY : all

all :
    @echo "command-1"

VAR := test
        
all :
    @echo "all : $(VAR)"

        我们来分析下,这份代码中有两个目标 all,那么我们在执行 make 的时候。它到底是执行哪个呢?一个可能是两个都执行,另一个就是执行第一个,因为默认的是执行第一个目标。下来我们来看看执行结果

make 的隐式规则(十一)

        我们看到它说 all 重复了,便忽略了前面的 all 命令。最终执行的是最后一个 all 命令。因此,当 makefile 中出现同名目标时,会将所有的依赖合并在一起,成为目标的最终依赖;当多处出现同一目标的命令时,make 发出警告,所有之前定义的命令被最后定义的命令取代注意:当使用 include 关键字包含其他文件时,需要确保被包含文件中的同名目标只要依赖,没有命令;否则,同名目标的命令将被覆盖!我们还是来看看,再建立一个新的 makefile.1

makefile.1 源码

all :
    @echo "this is command from makefile.1"

makefile 源码

.PHONY : all

VAR := test

all :
    @echo "all : $(VAR)"

include makefile.1

        我们来看看编译结果,看看打印出的是不是 all : test

make 的隐式规则(十一)

        我们看到输出的是 "this is command from makefile.1,并不是我们所期望的 all : test。其实也不难理解,因为我们包含的 makefile.1 中也包含了 all 命令,因此将上面的 all 给替换了。换句话说,这个现象有可能会给我们带来意想不到的结果。那么这也就属于 makefile 中的一条隐式规则了,什么是隐式规则呢?在 make 中,它提供了一些常用的,例行的规则实现;当相应目标的规则未提供时,make 尝试使用隐式规则。那么我们来看看下面这个 makefile 能编译成功吗?

.PHONY : all

SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)

app.out : $(OBJS)
    $(CC) -o $@ $^
    $(RM) $^
    @echo "Target ==> $@"

        我们看看编译的结果,是否会报错?

make 的隐式规则(十一)

        我们看到已经正确的编译了,而且结果也是对的。那么我们并没有在里面定义相应的规则啊,为什么就能正确编译呢?我们按照它的格式写个规则,再将 cc 换成 gcc 试试,看看结果是否会相同?

.PHONY : all

CC := gcc

SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)

app.out : $(OBJS)
    $(CC) -o $@ $^
    $(RM) $^
    @echo "Target ==> $@"

%.o : %.c
    @echo "my rule"
    $(CC)   -c -o $@ $^

        编译结果如下

make 的隐式规则(十一)

        结果和之前是一样的,只不过是输出了我们自定义的语句,将 cc 换成了 gcc。那么 cc 是什么呢?为何能编译源文件呢?cc 第一个 c 是 C 语言,第二个是 compiler 编译器的意思。那么这个 cc 编译器是哪来的呢?在原来的 Unix 系统中是有 cc 编译器的,因为它是商业版的,需要收费,因此在 Linux 系统中也是要支持 cc 的,不过此 cc 非彼 cc,我们来看看 cc 最后的原型是什么

make 的隐式规则(十一)

        我们看到 cc 最后指向的是 gcc,因此使用 cc 进行编译工作其实也是和使用 gcc 进行编译是一样的。那么上面的模式规则是谁来实现的呢?这个便是 makefile 中的隐式规则了。make 提供了生成目标文件的隐式规则,隐式规则会使用预定义变量完成编译工作;改变预定义变量将部分改变隐式规则的行为,当存在自定义规则时,不再使用隐式规则。当 make 发现目标的依赖不存在时,尝试通过依赖名逐一查找隐式规则,并且通过依赖名推导可能需要的源文件,如下

make 的隐式规则(十一)

        既然隐式编译这么强大,我们是不是就不用自己编写相关规则了呢?其实不是的,根据前辈们的经验总结。在实际的项目中,我们还是有必要禁止 makefile 中的隐式规则的,因为隐式规则有副作用。具体表现在:a> 编译行为难以控制,大量使用隐式规则可能产生意想不到的编译行为;b> 编译效率低下,make 从隐式规则和自定义规则中选择最终使用的规则。

        那么我们下来来看看隐式规则链。当依赖的目标不存在时,make 会极力组合各种隐式规则对目标进行创建,进而产生意料之外的编译行为!如:需要名为 N.o 的目标:N.y –> N.c –> N.o。我们还是以代码为例来进行分析说明

main.c 源码

#include <stdio.h>

extern void greeting();

int main()
{
    greeting();
    
    return 0;
}

func.p 源码(这只是一个测试的代码,用的是  Pascal 语言)

unit Func;

interface

procedure Greeting(); attribute (name = 'greeting');

implementation

procedure Greeting();
begin
  WriteLn('Hello, Pascal!');
end;

end.

makefile 源码

app.out : main.o func.o
    $(CC) -lstdc++ -o $@ $^

        那么我们此时想要用 func.c 实现某个功能,但是现在没有 func.c,所以编译时肯定会出错

make 的隐式规则(十一)

        我们看到 func.o 竟然会利用 func.p 来生成,不过最终还是出错了。出现这样的错误,我们是不是很纳闷呢?明明是没有对应的源文件,但是却报的是没有 pc 命令。这个便是 makefile 中的隐式规则了,那么 make 究竟提供了多少隐式规则?应如何查看查看隐式规则呢?查看隐式规则的方法是:查看所有的是 make -p;查看具体的规则是 make -p | grep "XXX"。下来我们来看看 make 中的隐式规则

make 的隐式规则(十一)

        因为所有的规则非常多,我们只截取了其中的一段,下来看看 %.o 对应的规则

make 的隐式规则(十一)

        我们看到它默认的是支持好多格式的,其中就包括 .c 和 .p 文件,因此它会将将我们之前的测试代码当成源文件进行编译了。那么我们应该如何避免它的隐式规则呢?在局部禁用的话,是直接在 makefile 中自定义规则或者在 makefile 中定义模式(如:%.o : %.p);全局禁用的话则使用 make -r。下来我们先来使用下局部禁用的方式,直接在 makefile 中定义模式,但是不做具体处理。

make 的隐式规则(十一)

        它直接就报错了,说没有相应的源文。我们再来看看使用全局禁用的方式

make 的隐式规则(十一)

        我们看到使用全局禁用的方式后,连 main.o 的文件也不能生成了。下来我们来说说后缀规则,它是旧式的“模式规则”,可以通过后缀描述的方式自定义规则。格式如下

make 的隐式规则(十一) 

        后缀规则分为双后缀规则和单后缀规则。双后缀规则是指定义一对文件后缀(依赖文件后缀和目标文件后缀),如:.cpp.o <==> %.o : %.cpp;单后缀规则是指定义单个文件后缀(源文件后缀),如:.c <==> % : %.c。后缀规则有这么几个注意事项:1、后缀规则中不允许有依赖;2、后缀规则必须有命令,否则无意义;3、后缀规则将逐步被模式规则所取代。

        下来我们还是以代码为例来进行分析说明,我们新建一个 func.c 文件用于说明问题

func.c 源码

#include <stdio.h>

void greeting()
{
    printf("void greeting : %s/n", "hello makefile!");
}

makefile 源码

app.out : main.o func.o
    $(CC) -lstdc++ -o $@ $^

.c.o :
    @echo "my suffix rule"
    $(CC) -o $@ -c $^

.c :
    @echo "my suffix rule"
    $(CC) -o $@ -c $^

        我们来看看编译结果

make 的隐式规则(十一)

        我们看到已经正确实现了。不过在现在的工程项目中,我们一般都会摒弃掉后缀规则,采用的都是模式规则。通过对 makefile 中隐式规则的学习,总结如下:1、当多处出现同一目标的命令时,只有最后定义的命令才有效;2、make 提供了一系列的隐式规则可使用,当 makefile 中未定义相关规则时,将尝试使用隐式规则;3、隐式规则中可能使用 make 中的预定义变量,改变预定义变量可部分改变预定义规则的行为;4、隐式规则可能造成意想不到的编译行为,在实际工程项目中尽量不使用隐式规则;5、后缀规则是一种旧式的模式规则,它正逐步被模式规则所取代。

        欢迎大家一起来学习 makefile,可以加我QQ:243343083

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/182989.html

(0)
上一篇 2021年11月2日
下一篇 2021年11月2日

相关推荐

发表回复

登录后才能评论