首页 > 其他分享 >学习makefile(2)

学习makefile(2)

时间:2024-09-08 21:18:42浏览次数:1  
标签:foo simple Makefile make makefile echo 学习 main

1、Makefile规则

   HelloWorld基本上所有程序员的所有编程语言一个最简单的例子,虽然makefile不是一门编程语言,但是我们同样可以在一个命令中终端上输出一个helloworld

all:
   echo"Hello World"

那上述例子中就有我们学习的第一个语法,echo前面必须只有TAB,且至少有一个TAB,不能用空格代替。(初学者易犯)

1.1目标 ,Makefile中第一个很重要的概念就是目标,上述例子中all就是我们的目标。

  特点:1.放在”:“的前面

     2.由字母和下划线组成

1.2 echo这句是一个生成目标的命令,echo是BASH shell中的一个命令,其功能是打印字符到终端上。

回归主题:all目标的定义,其实是定义了如何生成all目标我们也称之为规则。即定义了一个生成all目标的规则

all:
    echo "Hello world"
test:
    echo "test game"

 由此可见,⼀个 Makefile 中可以定义多个⽬标。调⽤ make 命令时,我们得告诉它我们的⽬标是什么,即要它⼲什么。当没有指明具体的⽬标是什么 时,那么 make 以 Makefile ⽂件中定义的第⼀个⽬标作为这次运⾏的⽬标。这“第⼀个”⽬标也称之 为默认⽬标(和是不是all没有关系)。当 make 得到⽬标后,先找到定义⽬标的规则,然后运⾏规则中的命令来达到构建⽬标的⽬的。

makefile中取消多余的命令行显示

在上面的指令中,多了很多的echo "......"的内容,这部分不是我们所期望的,如果要去掉,需要对上面的makefile进行一个改动,也就是在命令前加上一个@,这个符号就是告诉make,在运行的时候这一行命令不显示出来。

all:
    @echo "Hello world"
test:
    @echo "test game" 

紧接着对makefile进行如下的改动,在all的后面加上test

all: test
    @echo "Hello world"
test:
    @echo "test game"

 

   

 

 

 

⼀个规则是由⽬标(targets)、先决条件(prerequisites)以及命令(commands)所组成的。
需要指出的是,⽬标和先决条件之间表达的就是依赖关系(dependency),这种依赖关系指明在构建⽬标之前,必须保证先决条件先满⾜(或构建)。⽽先决条件可以是其它的⽬标,当先决条件是⽬标时,其必须先被构建出来。还有就是⼀个规则中⽬标可以有多个,当存在多个⽬标,且这⼀规则是 Makefile 中的第⼀个规则时,如果我们运⾏ make 命令不带任何⽬标,那么规则中的第⼀个⽬标将被视为是缺省⽬ 标。

规则的功能就是指明 make 什么时候以及如何来为我们重新创建⽬标,在 Hello World 例⼦中,不论我们 在什么时候运⾏ make 命令(带⽬标或是不带⽬标),其都会在终端上打印出信息来,和我们采⽤ make 进⾏代码编译时的表现好象有些不同。当采⽤ Makefile 来编译程序时,如果两次编译之间没有任何代码 的改动,理论上说来,我们是不希望看到 make 会有什么动作的,只需说“⽬标是最新的”,⽽我们的最终 ⽬标也是希望构建出⼀个“聪明的” Makefile 的。与 Hello World 相⽐不同的是,采⽤ Makefile 来进⾏ 代码编译时,Makefile 中所存在的先决条件都是具体的程序⽂件,后⾯我们会看到。

 

简单举例:makefile原理

all: main.o foo.o
    gcc -o simple main.o foo.o
main.o: main.c
    gcc -o main.o -c main.c
foo.o: foo.c
    gcc -o foo.o -c foo.c
clean:
    rm simple main.o foo.o

 编译:

 

上面的展示了测试结果,注意到了第⼆次编译并没有构建⽬标⽂件的动作吗?但为什么有构建simple可执⾏程序的动作呢?为了明⽩为什么,我们需要了解 make 是如何决定哪些⽬标(这⾥是⽂件)是需要重新编译的。为什么 make会知道我们并没有改变 main.c 和 foo.c 呢?答案很简单,通过⽂件的时间戳!当 make 在运⾏⼀个规则时,我们前⾯已经提到 了⽬标和先决条件之间的依赖关系,make 在检查⼀个规则时,采⽤的⽅法是:如果先决条件中相关的⽂件的时间戳⼤于⽬标的时间戳,即先决条件中的⽂件⽐⽬标更新,则知道有变化,那么需要运⾏规则当中 的命令重新构建⽬标。这条规则会运⽤到所有与我们在 make时指定的⽬标的依赖树中的每⼀个规则。⽐如,对于 simple 项⽬,其依赖树中包括三个规则,make 会检查所有三个规则当中的⽬标(⽂件)与先决条件(⽂件)之间的时间先后关系,从⽽来决定是否要重新创建规则中的⽬标。

 

问题一:第二次构建的时候为什么simple会被重新构建?

是因为simple文件不存在,我们在这次构建的目标是all,而all在我们编译的过成中并不生成,所以第二次make的时候找不到,所以又重新编译了一遍。

修改makefile

simple: main.o foo.o
    gcc -o simple main.o foo.o
main.o: main.c
    gcc -o main.o -c main.c
foo.o: foo.c
    gcc -o foo.o -c foo.c
clean:
    rm simple main.o foo.o

结果

一个文件是否改变不是看这个文件的大小是否改变,而是看这个文件的时间戳是否发生了变化。可以直接使用touch指令对文件的时间戳进行修改。

假目标

如上图,如果我们的创建了一个clean文件之后,继续去运行make clean,这时候不是按照我们前面运行的make clean进行清理文件。

为什么出现上面的原因?

因为这个时候make 将clean单程是一个文件,并且在当前的目录下找到了这个文件,再加上clean目标没有任何的先决条件,这时候进行make clean时,系统会认为clean是最新的

 

使用假目标,假目标最从常用清净就是避免所定义的目标和的已经存在文件是从重名的情况,假⽬标可以采⽤.PHONY 关键字来定义,需要注意的是其必须是⼤写字⺟。

采⽤.PHONY 关键字声明⼀个⽬标后,make 并不会将其当作⼀个⽂件来处理,⽽只是当作⼀个概念上的⽬标。对于假⽬标,我们可以想像的是由于并不与⽂件关联,所以每⼀次 make 这个假⽬标时,其所在的规则中的命令都会被执⾏。

.PHONY: clean
simple: main.o foo.o
    gcc -o simple main.o foo.o
main.o: main.c
    gcc -o main.o -c main.c
foo.o: foo.c
    gcc -o foo.o -c foo.c
clean:
    rm simple main.o foo.o

 采⽤.PHONY 关键字声明⼀个⽬标后,make 并不会将其当作⼀个⽂件来处理,⽽只是当作⼀个概念上的⽬标。对于假⽬标,我们可以想像的是由于并不与⽂件关联,所以每⼀次 make 这个假⽬标时,其所在的规则中的命令都会被执⾏。

变量

.PHONY: clean
CC = gcc
RM = rm
EXE = simple
OBJS = main.o foo.o
$(EXE): $(OBJS)
    $(CC) -o $(EXE) $(OBJS)
main.o: main.c
    $(CC) -o main.o -c main.c
foo.o: foo.c
    $(CC) -o foo.o -c foo.c
clean:
    $(RM) $(EXE) $(OBJS)

变量的使用可以提高makefile的可维护性。⼀个变量的定义很简单,就是⼀个名字(变量名)后⾯跟上⼀个等号,然后在等号的后⾯放这个变量所期望的值。对于变量的引⽤,则需要采⽤$(变量名)或者${变量名}这种模式。在这个 Makefile 中,我们引⼊了 CC 和 RM 两个变量,⼀个⽤于保存编译器名,⽽另⼀个⽤于指示删除⽂件的命令是什么。还有就是引⼊了 EXE 和 OBJS 两个变量,⼀个⽤于存放可执⾏⽂件名,可另⼀个则⽤于放置所有的⽬标⽂件名。采⽤变量的话,当我们需要更改编译器时,只需更改变量赋值的地⽅,⾮常⽅便,如果不采⽤变量,那我们得更改每⼀个使⽤编译器的地⽅,很是麻烦。

 

1.自动变量(☆☆☆☆☆)

对于每⼀个规则,⽬标和先决条件的名字会在规则的命令中多次出现,每⼀次出现都是⼀种麻烦,更为麻烦的是,如果改变了⽬标或是依赖的名,那得在命令中全部跟着改。有没有简化这种更改的⽅法呢?这我们需要⽤到 Makefile 中的⾃动变量,最常用包括:

  • $@⽤于表示⼀个规则中的⽬标。当我们的⼀个规则中有多个⽬标时,$@所指的是其中任何造成命令被运⾏的⽬标。
  • $^则表示的是规则中的所有先择条件。
  • $<表示的是规则中的第⼀个先决条件。

 在 Makefile 中‘$’具有特殊的意思,因此,如果想采⽤ echo 输出‘$’,则必需⽤两个连着的‘$’。还有就是,$@对于 Shell 也有特殊的意思,我们需要在“$$@”之前再加⼀个脱字符‘\’。

 

2.特殊变量

(1)MAKE变量

它表示的是make 命令名是什么。当我们需要在 Makefile 中调⽤另⼀个 Makefile 时需要⽤到这个变量,采⽤这种⽅式,有利于写⼀个容易移植的 Makefile。

(2)MAKECMDGOALS变量

它表示的是当前⽤户所输⼊的 make ⽬标是什么。MAKECMDGOALS 指的是⽤户输⼊的⽬标,当我们只运⾏ make 命令时,虽然根据
Makefile 的语法,第⼀个⽬标将成为缺省⽬标,即 all ⽬标,但 MAKECMDGOALS 仍然是空,⽽不是
all,这⼀点我们需要注意。

3.递归扩展变量

示例了使⽤等号进⾏变量定义和赋值,对于这种只⽤⼀个“=”符号定义的变量,我们称之为递归扩展变量(recursively expanded variable)。

.PHONY: all
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
    @echo $(foo)

除了递归扩展变量还有⼀种变量称之为简单扩展变量(simply expanded variables),是⽤“:=”操作符
来定义的。对于这种变量,make 只对其进⾏⼀次扫描和替换。

另外还有一种条件赋值符“?=”,条件赋值的意思是当变量以前没有定义时,就定义它并且将左边的值赋值给它,如果已经定义了那么就不再改变其值。条件赋值类似于提供了给变量赋缺省值的功能。

此外,还有"+="操作符,对变量进⾏赋值的⽅法

4.override指令

在设计 Makefile 时,我们并不希望⽤户将我们在 Makefile 中定义的某个变量覆盖掉,那就得⽤ override 指令了。

.PHONY: all
override foo = x
all:
    @echo "foo = $(foo)"

 

5.模式

如果对于每⼀个⽬标⽂件都得写⼀个不同的规则来描述,那会是⼀种“体⼒活”,太繁了!对于⼀个⼤型项⽬,就更不⽤说了。Makefile 中的模式就是⽤来解决我们的这种烦恼的。

.PHONY: clean
CC = gcc
RM = rm
EXE = simple
OBJS = main.o foo.o
$(EXE): $(OBJS)
    $(CC) -o $@ $^
%.o: %.c
    $(CC) -o $@ -c $^
clean:
    $(RM) $(EXE) $(OBJS)

与 simple 项⽬前⼀版本的 Makefile 相⽐,最为直观的改变就是从⼆条构建⽬标⽂件的规则变成了⼀条。
模式类似于我们在 Windows 操作系统中所使⽤的通配符,当然是⽤“%”⽽不是“*”。采⽤了模式以后,
不论有多少个源⽂件要编译,我们都是应⽤同⼀个模式规则的,很显然,这⼤⼤的简化了我们的⼯作。使
⽤了模式规则以后,你同样可以⽤这个 Makefile 来编译或是清除 simple 项⽬,这与前⼀版本在功能上是
完全⼀样的。

6.函数

函数是 Makefile 中的另⼀个利器,现在我们看⼀看采⽤函数如何来简化 simple 项⽬的 Makefile。对于
simple 项⽬的 Makefile,尽管我们使⽤了模式规则,但还有⼀件⽐较恼⼈的事,我们得在这个Makefile
中指明每⼀个需要被编译的源程序。对于⼀个源程序⽂件⽐较多的项⽬,如果每增加或是删除⼀个⽂件都
得更新 Makefile,其⼯作量也不可⼩视!

采⽤了 wildcard 和 patsubst 两个函数后 simple 项⽬的 Makefile。可以先⽤它来编译⼀下 simple 项⽬代码以验证其功能性。需要注意的是函数的语法形式很是特别,对于我们来说只要记住其形式就⾏了。

.PHONY: clean
CC = gcc
RM = rm
EXE = simple
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SRCS))
$(EXE): $(OBJS)
    $(CC) -o $@ $^
%.o: %.c
    $(CC) -o $@ -c $^
clean:
    $(RM) $(EXE) $(OBJS)

(1)addprefix函数

addprefix 函数是⽤来在给字符串中的每个⼦串前加上⼀个前缀,其形式是:$(addprefix prefix, names...)

.PHONY:all
without_dir= foo.c bar.c main.c
with_dir:=$(addprefix objs/, $(without_dir))
all:
    @echo $(with_dir)

 

(2)filter函数

filter 函数⽤于从⼀个字符串中,根据模式得到满⾜模式的字符串,其形式是:$(filter pattern..., text)

.PHONY: all
sources = foo.c bar.c baz.s ugh.h
sources := $(filter %.c %.s, $(sources))
all:
    @echo $(sources)

运行结果

 

(3)filter-out函数

filter-out 函数⽤于从⼀个字符串中根据模式滤除⼀部分字符串,其形式是:$(filter-out pattern..., text)

(4)patsubst函数(☆☆☆)

patsubst 函数是⽤来进⾏字符串替换的,其形式是:$(patsubst pattern, replacement, text)

 

.PHONY:all
mixed=foo.c bar.c main.o
objects:=$(patsubst %.c, %.o, $(mixed))
all:
    @echo $(objects)

 

 

 

 

(5)strip

 

strip 函数⽤于去除变量中的多余的空格,其形式是:$(strip string)

(6)wildcard函数(☆☆☆)

wildcard 是通配符函数,通过它可以得到我们所需的⽂件,这个函数类似我们在 Windows 或Linux 命
令⾏中的“*”。其形式是:$(wildcard pattern)

.PHONY:all
SRC=$(wildcard *.c)
all:
    @echo "SRC = $(SRC)"

 

标签:foo,simple,Makefile,make,makefile,echo,学习,main
From: https://www.cnblogs.com/gunancheng/p/18344244

相关文章

  • FZU软件工程课程学习过程中的自我评估以及软件工程学习指南
    这个作业属于哪个课程https://edu.cnblogs.com/campus/fzu/SE2024这个作业要求在哪里https://edu.cnblogs.com/campus/fzu/SE2024/homework/13243这个作业的目标初步了解博客园和github的使用,体会AIGC在辅助完成任务上的效率和便捷,理解程序与程序员在AI时代的关系......
  • Hadoop学习总结
    在深入学习Hadoop的过程中,我逐渐探索了其高级特性和性能优化的技巧,发现这些方法对提升数据处理的效率和集群的稳定性至关重要。Hadoop,作为一个强大的大数据处理框架,其核心组件包括HDFS(分布式文件系统)和YARN(YetAnotherResourceNegotiator)。在掌握了基本的使用方法后,我开始关注如......
  • 学习之路(二)
    今天是24.9.8已经开学两周了继续写流水账:暑假确实和上一篇说的一样只回家呆了几天,一直留在学校这边做小项目。这次做的项目和之前的练手不同了,因为一周前真的上线应用了,上线当天就突破1k用户(当然依靠的是老师在迎新时的推广)。现在回过头来看,练手的项目和需要上线的项目真......
  • 《动手学深度学习》笔记3——矩阵求导
    李沐老师的讲解思路是先从数学概念引入,讲完以后再到代码实现:1.数学概念1.1标量导数1.2向量求导(梯度)分为四种情况:1.2.1标量y,关于向量x求导李沐老师这里先讲了y为标量,x为向量的情况,x是长度为1的列向量,关于列向量的导数(即梯度)是行向量,具体解释如下:在这个例子里, ......
  • 《动手学深度学习》笔记4——线性回归 + 基础优化算法
    李沐老师:线性回归是机器学习最基础的一个模型,也是我们理解之后所有深度学习模型的基础,所以我们从线性回归开始1.线性回归由于是案例引入,没有很难的知识点,咱直接贴上李沐老师的PPT:1.1线性模型--单层神经网络李沐老师:神经网络起源于神经科学,但现在深度学习的发展......
  • 深度学习|激活函数:网络表达增强
    文章目录引言常见的激活函数阶跃函数**Sigmoid****ReLU****LeakyReLU****Softmax****Tanh**恒等函数对比分析梯度问题可训练性结语引言在前文对M-P神经元结构的介绍以及「深度学习|模型推理:端到端任务处理」的推理过程演示中,我们反复提到了激活函数......
  • 强化学习指南:训练过程与评估过程的区别
    强化学习指南:训练过程与评估过程的区别在强化学习(RL)中,训练和评估是两个截然不同但密切相关的过程。本指南将详细解释这两个过程的区别,以及如何正确实施它们。1.训练过程训练是RL中最核心的部分,它是智能体学习策略的过程。1.1训练的目的训练的目的是让智能体通过与环......
  • 深入解析多智能体强化学习算法的训练效率
    深入解析多智能体强化学习算法的训练效率在多智能体强化学习(MARL)领域,不同算法的训练效率和最终性能差异显著。本文将深入分析几种主流MARL算法的训练特性,探讨影响其效率的关键因素。1.算法概览我们将讨论以下几种典型的MARL算法:VDN(ValueDecompositionNetworks)QM......
  • AI苏格拉底提问学习记录
       ......
  • 学霸带你探索团队学习的最佳实践与策略
    团队学习的力量与实践团队学习不仅是组织成功的关键,也是提升个人技能和效率的重要手段。随着游戏和工作环境的不断复杂化,单一的学习模式已经无法满足现代团队的需求。团队学习不仅可以提高整体表现,还能通过集体智慧来解决复杂问题。在这篇文章中,我们将深入探讨如何通过有效的......