嵌入式Linux应用开发基础知识(四)——Makefile语法

本文详细介绍了Makefile中的通配符使用,如%.o和%.c,以及如何通过$@和$<、$^等变量优化规则。还探讨了假想目标的概念,例如清理中间文件的clean目标,并解决了因实际存在的clean文件导致的问题。此外,文章讲解了Makefile中即时变量和延时变量的区别,并通过实例展示了变量的赋值和运算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面我们介绍了Makefile运行规则,这一章我们介绍Makefile的语法。

1. 通配符

什么是通配符?学过其他编程语言的可能只要什么叫通配符,通配符是一种特殊语句,主要有星号(*)和问号(?),用来模糊搜索文件。当查找文件夹时,可以使用它来代替一个或多个真正字符。

1.1 Makefile常见通配符

Makefile具有较多通配符,但是常见的也就几种。如下所示:

  • %.o或%.c : 该通配符表示任意的.o文件或者.c文件
  • $@ : 表示目标
  • $< : 表示第一个依赖文件
  • $^ : 表示所有依赖文件

1.2 Makefile常见通配符实验

以下为上节课我们使用的Makefile文件

test : main.o sub.o
        gcc -o test main.o sub.o
main.o : main.c
        gcc -c -o main.o main.c
sub.o : sub.c
        gcc -c -o sub.o sub.c

我们将使用通配符进行改进,实现我们使用%.o和%.c文件进行改进,

test : main.o sub.o
        gcc -o test main.o sub.o
%.o : %.c
        gcc -c -o main.o main.c

运行逻辑:

  • 当一开始运行程序时,没有main.o,那么会向下查找,发现通配符%.o, 那么此时将%替换为main,执行: gcc -c -o main.o main.c 命令
  • 生成main.o后,再次查找依赖sub.o, 发现没有改文件,那么需要往下查找,此时将%替换为sub, 执行gcc -c -o main.o main.c命令,不过意外的是,这里还是生成main.o, 最后还会编译不了 test, 系统报错,相应过程见下图。
    在这里插入图片描述
    需要对上述Makefile进行修改,使用
test : main.o sub.o
        gcc -o test main.o sub.o
%.o : %.c
        gcc -c -o $@ $<

$@表示目标 , $<表示第一个依赖文件, 那么当%为main时,此时 $@ 表示main.o, $<表示main.c。当%为sub时,此时 $@ 表示sub.o, $<表示sub.c。 具体运行过程见下图:

在这里插入图片描述
这样就能正常运行了, 不过是否考虑到一个问题,如果有很多OBJ文件呢? 难道我们需要一个一个加上XX.o吗,那么此时gcc -o test main.o sub.o这条语句得使劲加?显然这样是不智能的,我们可以使用$^ : 通配符,表示所有依赖文件,改进的程序如下:

test : main.o sub.o
        gcc -o test $^
%.o : %.c
        gcc -c -o $@ $<

当运行 gcc -o test $^ 命令时,程序会将 $^ 替换为main.o sub.o, 这样的话我们有多个源文件,只需要往sub.o后添加就可以了。下图为运行过程图:
在这里插入图片描述

2. 假想目标

我们先不提什么是假想目标,我们慢慢引入。

2.1 清除中间文件

我们希望删除中间文件(.o文件)和目标文件(test), 以确保我们将编译过程产生的文件全部删除,我们可以在Makefile文件中加入 rm *.o test命令,其中 *.o表示全部的OBJ文件,test为可执行程序。那这条语句应该怎么加入到Makefile文件中呢?我们可以仿造一下,直接写clean: (不需要依赖文件)。当我们输入make时(不带任何参数),此时系统会找到文件的第一个目标文件(第一行的开头的目标:test), 默认执行make test, 此时系统运行第一行,在清楚时,输入make clean, 系统会跳转到clean处,由于不需要依赖文件,所以任何时间都会运行,此时执行rm *.o test 以下为所有代码

test : main.o sub.o
        gcc -o test $^
%.o : %.c
        gcc -c -o $@ $<
clean:
		rm *.o test

下图为实验过程:
在这里插入图片描述

2.2 本地存在clean文件

是否想过一个问题,如果在当前目录存在clean文件,那么当我们运行make clean时会出现什么情况?
在这里插入图片描述

如上图所示:我们使用 > clean命令创建一个空的clean文件,然后再执行make clean, 此时发现显示:make: ‘clean’ is up to date. 这个报错表示clean文件是最新的, 不需要再执行了,这样就不能执行make clean操作了,此时我们可以使用.PHONY: clean 设置clean为假想目标,详细代码如下:

test : main.o sub.o
        gcc -o test $^
%.o : %.c
        gcc -c -o $@ $<
clean:
		rm *.o test
.PHONY: clean

下图为详细的试验过程
在这里插入图片描述

3. 变量

3.1 分类及定义

Makefile的变量分为即时变量(也称简单变量)、延时变量,即时变量是在定义时就确定值,而延时变量是在使用时才确定的值,在没使用 时一直为空,以下为使用举例:

  • A := xxx # A的值即刻确定,在定义时即确定
  • B = xxx # B的值使用到时才确定

3.2 实验

3.2.1 使用变量

Makefile中使用变量使用$(变量名称),代码中的all和之前讲的clean类似, 由于代码中只有一个目标文件all,所以在运行时输入make和make all的效果是一样的,echo是Makefile中的打印函数,测试代码如下:

A := abc
B = 123

all:
        echo $(A)
        echo $(B)

运行结果:
在这里插入图片描述
上述图中echo abc和echo 123是打印命令,如果不想打印,可以在echo前面加入@,代码如下:

A := abc
B = 123

all:
        @echo $(A)
        @echo $(B)

运行结果:
在这里插入图片描述
此时我们还是看不出即时变量和延时变量的区别,接着我们引入一个新变量C,代码如下:

A := $(C)
B = $(C)
C = abc

all:
        @echo $(A)
        @echo $(B)

运行结果:
在这里插入图片描述
有上图可知,A变量值为空, B变量的值为abc。
运行机制如下:运行A := $©时,由于C变量未定义,而且A为即时变量,此时A的值已经确定(为空),运行B = $©时,即使C变量未定义,但是B为延时变量,在引用时才确定值,运动C = abc,定义了延时变量C,运行@echo $(A)直接打印A的值(空),运行@echo $(B)此时先引用B,在引用时C变量已经定义,有值(abc),故此时B的值也确定了(也为abc),故打印abc。
为了更加清晰的看出变量的值,我们加上辅助信息,代码如下:

A := $(C)
B = $(C)
C = abc

all:
        @echo A = $(A)
        @echo B = $(B)

运行结果:
在这里插入图片描述

有人会想是否和C = abc的位置有关呢?我们修改代码如下:

A := $(C)
B = $(C)
# C = abc

all:
        @echo A = $(A)
        @echo B = $(B)
C = abc

运行结果:
在这里插入图片描述
由运行结果可知,可位置是没关系的,是因为Makefile文件在运行前会将所有代码读入,当运行make或make all时才生产目标文件。那么我们是否会考虑代码加载的顺序?是从上到下加载吗?我们不妨做个实验:

A := $(C)
B = $(C)
C = abc

all:
        @echo A = $(A)
        @echo B = $(B)
C = 123

观察以上代码,如果打印B=123,表示是顺序执行的,并且后面的值覆盖了前面的值
运行结果:
在这里插入图片描述
从上图结果可以看出,的确和我们的猜想是一样的。

3.3 变量运算及赋值方式

3.3.1 常见的赋值方式

  • ?= : 也是延时变量赋值的一种,区别在于如果是第一次定义才起效,如果在前面该变量已定义则忽略这句。
  • +=:附加,它是即时变量还是延时变量取决于前面的定义

3.3.2 赋值方式实验

我们做 += 的实验,运行如下代码:

A := $(C)
B = $(C)
C = abc

all:
        @echo A = $(A)
        @echo B = $(B)
C += 123

运行结果:
在这里插入图片描述
由结果可知,C还是为即时变量,因为之前是采用 =进行定义的。

我们再做 ?= 的实验,运行如下代码:

A := $(C)
B = $(C)
C = abc

D = LingTu
D ?= LLLS

all:
        @echo A = $(A)
        @echo B = $(B)
        @echo D = $(D)
C += 123

运行结果:
在这里插入图片描述
由上图可知,D = LingTu在D ?= LLLS之前定义了, 故D ?= LLLS定义不起效果。
我们再做一个对比,代码如下:

A := $(C)
B = $(C)
C = abc

D ?= LLLS

all:
        @echo A = $(A)
        @echo B = $(B)
        @echo D = $(D)
C += 123

运行结果:
在这里插入图片描述
此时D ?= LLLS为第一次定义变量,故起效果。
除了在代码中幅值外,我们也可以在输入命令时赋值,命令行的赋值先于代码中的赋值,实验如下:
在这里插入图片描述
注意命名行赋值时参数不要输入空格,命令行是默认以空格间隔参数的,第一次加入空格报错了。

详细易懂的Linux makefile教程 一、概述 什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。 因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。 makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。 现在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。当然,不同产商的make各不相同,也有不同的语法,但其本质都是在“文件依赖性”上做文章,这里,我仅对GNU的make进行讲述,我的环境是RedHat Linux 8.0,make的版本是3.80。必竟,这个make是应用最为广泛的,也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的(POSIX.2)。 在这篇文档中,将以C/C++的源码作为我们基础,所以必然涉及一些关于C/C++的编译的知识,相关于这方面的内容,还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。 二、关于程序的编译和链接 —————————— 在此,我想多说关于程序编译的一些规范和方法,一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。 ...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

零涂

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值