make 的隐式规则(十一)

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

.PHONY : all

all :
    @echo "command-1"

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

我们来分析下,这份代码中有两个目标 all,那么我们在执行 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

我们看到输出的是 "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 [email protected] $^
    $(RM) $^
    @echo "Target ==> [email protected]"

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

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

.PHONY : all

CC := gcc

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

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

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

编译结果如下

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

我们看到 cc 最后指向的是 gcc,因此使用 cc 进行编译工作其实也是和使用 gcc 进行编译是一样的。那么上面的模式规则是谁来实现的呢?这个便是 makefile 中的隐式规则了。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 [email protected] $^

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

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

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

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

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

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

后缀规则分为双后缀规则和单后缀规则。双后缀规则是指定义一对文件后缀(依赖文件后缀和目标文件后缀),如:.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 [email protected] $^

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

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

我们来看看编译结果

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

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

原文地址:http://blog.51cto.com/12810168/2130403

时间: 2024-10-04 04:13:32

make 的隐式规则(十一)的相关文章

Makefile中的隐式规则

Makefile中的隐式规则 1.隐式规则中的变量 隐式规则中使用的变量分成两种:一种是命令相关的,如"CC":一种是参数相关的,如"CFLAGS". 与命令相关的变量 变量 含义 AR 函数库打开包程序.默认命令是"ar" AS 汇编语言编译程序.默认命令是"as" CC C语言编译程序.默认命令是"cc" CXX C++语言编译程序.默认命令是"g++" CO 从RCS文件中扩展文件

第15课 - make的隐式规则(上)

第15课 - make的隐式规则(上) 1. 问题 如果把同一个目标的命令拆分的写到不同地方,会发生什么? 执行make all 这个实验表明了:如果同一个目标的命令拆分的写到不同地方,那么 make 会覆盖之前的目标对应的命令,使用最新出现的目标对应的命令. makefile 中出现同名目标时 - 依赖: 所有的依赖将合并在一起,成为目标的最终依赖 - 命令: 当多处出现同一目标的命令时,make 发出警告 所有之前定义的命令被最后定义的命令取代 注意:当使用 include 关键字包含其它文

makefile(06)_隐式规则

15.Make的隐式规则 15.1.命令覆盖 问题1:通过各目标的命令拆分写到不同的地方,会发生什么?Makefile中出现同名目标时:依赖:所有的依赖将合并到一起,成为目标的最终依赖命令:当多处出现同一目标的命令时,make发出警告,所有之前定义的命令被最后的命令取代.注意:当使用include包含其他文件(makefile)时,需要确保被包含的文件中的同名目标只有依赖,没有命令:否则,同名目标的命令将被覆盖! 15.2.隐式规则 Make中提供了一些常用的,例行的规则实现,当目标的规则未提供

make--隐式规则 路径搜索及实例

一.隐式规则 问题一 .PHONY : all all: @echo "command-1" VAR:=test all: @echo "all:$(VAR)" make之后的结果如图所示可以得出的结论是A.makefile中出现同名目标时依赖:所有的依赖将合并在一起,成为目标的最终依赖命令:当多处出现同一目标时,make发出警告:所有之前定义的命令被最后定义的命令取代注意:当使用include关键字包含其他文件时,需要确保被包含文件中的同名目标只有依赖,没有命令:

oracle数据类型及其隐式转换

oracle有三种最基本的数据类型,即字符型.数值型.日期型. oracle提供的单行函数中,针对不同的数据类型,提供大量实用的函数,同时提供一系列数据类型转换函数,如下: 1)to_char     数值.日期->字符型     语法:to_char(num|date,[format mask],[nls_parameters])     参数:num|date 待转换的数值或者日期             format mask:可选参数 数字->字符型的可用格式 格式元素 元素说明 格式

转载:深入理解Scala的隐式转换系统

摘要: 通过隐式转换,程序员可以在编写Scala程序时故意漏掉一些信息,让编译器去尝试在编译期间自动推导出这些信息来,这种特性可以极大的减少代码量,忽略那些冗长,过于细节的代码. 使用方式: 1.将方法或变量标记为implicit 2.将方法的参数列表标记为implicit 3.将类标记为implicit Scala支持两种形式的隐式转换: 隐式值:用于给方法提供参数 隐式视图:用于类型间转换或使针对某类型的方法能调用成功 隐式值: 例1:声明person方法.其参数为name,类型String

函数原型prototype以及对象的隐式原型__prot0__的基本了解

prototype原型: 一. 函数与对象的关系    1. 函数是对象的一种(函数是对象类型)        例: function fn1(){.........}           console.log(fn1 instanceof Object);           返回true,说明函数(fn1)是对象类型. 2. 对象是由函数创建的       例: var obj = new Object();           var arr = new Array(3);       

模板显式、隐式实例化和(偏)特化、具体化的详细分析(转)

这么多叫法,其实就是三种. 1. 显示实例化 2. 隐式实例化 3. 特化(=具体化).偏特化 一.实例化 1.显示.隐式实例化 什么是实例化:一个通过使用具体值替换模板参数,从模板产生的普通类,函数或者成员函数的过程. 显示实例化:通过名字可见,就是清楚的表明你要实例化的类型 隐式实例化:通过编译器自己推测判断要实例化的类型. 比如一个模板: template<class T> //函数模板实现  void swap(T &a, T &b) {     T temp;    

数据类型回顾——数据类型转换(显式和隐式)—JS学习笔记2015-6-3(第47天)

对于JS这种语言来说,因为它是一种动态类型语言,变量是没有类型的,可以随时赋予任意值. 但是,数据本身和各种运算是有类型的,因此运算时变量需要转换类型. 大多数情况下,这种数据类型转换是自动的,但是有时也需要手动强制转换. 首先看下强制类型转换(显式) 之前提到的Namber.parseInt.parseFloat 都是强制类型转换: 这里在看阮一峰博客(http://javascript.ruanyifeng.com/grammar/conversion.html#toc1) Number方法