Makefile中的:=和=有什么区别?

200
在Make中,变量赋值可以使用 := 和 = 运算符,它们之间有什么区别?

10
可能是Makefile变量赋值的重复问题。 - eldarerathis
7个回答

182

简单赋值 :=

简单赋值表达式仅在第一次出现时进行计算。例如,如果在首次遇到时 CC :=${GCC} ${FLAGS} 被计算为 gcc -W , 那么每次出现 ${CC} 时,它都将被替换为 gcc -W

递归赋值 =

递归赋值表达式在代码中每次遇到变量时都会重新计算。例如,如果有类似于 CC = ${GCC} {FLAGS} 的语句, 只有当执行像 ${CC} file.c 这样的操作时,它才会被计算。但是,如果重新分配了变量 GCC,即 GCC=c++ ,那么在重新赋值后,${CC} 将转换为 c++ -W

条件赋值 ?=

条件赋值仅在变量没有值的情况下为其赋值

追加赋值 +=

假设 CC = gcc,则追加运算符使用方式为 CC += -w,然后 CC 现在的值为 gcc -W

了解更多,请查看这些教程


9
“一个简单的赋值表达式只会在第一次出现时被评估一次”,也就是说,在变量定义时进行扩展或评估,而不是在第一次使用时进行。 - Michael Burr

121

这在GNU Make文档的6.2变量的两种类型一章中有详细说明。

简而言之,使用:=定义的变量只会被扩展一次,但使用=定义的变量会在每次使用时被扩展。


5
那么,把 := 称作更高效的方式是正确的吗?或者说,在 Makefiles 中效率并不是一个真正的因素? - Ungeheuer
5
这并不是问题,因为调用过程(make的主要工作)比解析内部变量的开销大得多。 - Kirill Bulygin

63
对于我来说,最好的实践方式就是在这个Makefile片段中看到它的使用:

简单赋值

XX := $(shell date) # date will be executed once
tt:
    @echo $(XX)
    sleep 2
    @echo $(XX)

运行中

make tt

将会产生:

sex 22 jan 2021 14:56:08 -03
sex 22 jan 2021 14:56:08 -03

(相同的值)

扩展赋值

XX = $(shell date) # date will be executed every time you use XX
tt:
    @echo $(XX)
    sleep 2
    @echo $(XX)

运行中

make tt

将会产生:

sex 22 jan 2021 14:56:08 -03
sex 22 jan 2021 14:56:10 -03

不同的值


1
在Makefile中,//不是有效的注释。 - Eric
1
有趣的选择使用$(shell sleep 2)而不是直接调用sleep 2。两者之间的结果确实非常不同。你为什么选择了$(shell ...) - Eric
@Eric,我修复了注释的使用。感谢你的建议。关于使用 shell sleep 2,我认为只使用 sleep 2 就可以很好地工作。在创建这个答案时,我从一个 Makefile 中复制了这段代码,用于将其结果分配给一个变量,这就是使用 $(shell ...) 的原因,类似于:result:=$(find . -type f -name '*js' - Edgard Leal
1
这个答案,我更喜欢。 - daparic

12

6

虽然这是一个老问题,但是这个例子帮助我理解区别,特别是在我忘记时。

使用以下Makefile运行make将立即退出:

a = $(shell sleep 3)

使用以下Makefile运行make将会等待3秒钟,然后退出:

a := $(shell sleep 3)

在前一个 Makefile 中,只有在 Makefile 中的其他位置使用 a 时才会对其进行评估,而在后一个 Makefile 中,即使没有使用它,a 也会立即得到评估。

1

递归赋值操作=每次使用时都会被计算,但不是按照在配方命令中遇到的顺序进行计算,而是在运行任何配方命令之前计算。

根据以下示例:

default: target1 target2

target1 target2:
    @echo "Running at:           `gdate +%s.%N`"
    @echo "Simple assignment:    $(SIMPLE_ASSIGNMENT)"
    @echo "Recursive assignment: $(RECURSIVE_ASSIGNMENT)"
    sleep 1
    @echo "Running at:           `gdate +%s.%N`"
    @echo "Simple assignment:    $(SIMPLE_ASSIGNMENT)"
    @echo "Recursive assignment: $(RECURSIVE_ASSIGNMENT)"
    @echo


SIMPLE_ASSIGNMENT := $(shell gdate +%s.%N)
RECURSIVE_ASSIGNMENT = $(shell gdate +%s.%N)

输出:

 make
Running at:           1645056840.980488000
Simple assignment:    1645056840.949181000
Recursive assignment: 1645056840.958590000
sleep 1
Running at:           1645056842.008998000
Simple assignment:    1645056840.949181000
Recursive assignment: 1645056840.969616000

Running at:           1645056842.047367000
Simple assignment:    1645056840.949181000
Recursive assignment: 1645056842.027600000
sleep 1
Running at:           1645056843.076696000
Simple assignment:    1645056840.949181000
Recursive assignment: 1645056842.035901000

1
= 被称为递归展开变量或懒展开变量。在下面的示例中,当 make 读取此行时
VAR1 = $(VAR1) + 100

将右侧VAR1的值仅存储到左侧VAR1中,而不进行扩展。当make尝试读取定义在左侧的$(VAR1)时,这将导致无限循环。

VAR1 = 10
VAR2 = 20
VAR3 = 30

# Lazy initialization
VAR1 = $(VAR1) + 100

default:
    echo $(VAR1)

输出:

% make
Makefile:6: *** Recursive variable `VAR1' references itself (eventually).  Stop.

:=称为扩展变量或即时变量

当读取这行代码时,右侧的变量将被扩展并将结果值保存到左侧。请参阅下面的程序输出

VAR1 = 10
VAR2 = 20
VAR3 = 30

# instant initialization
VAR1 := $(VAR1) + 100

default:
    echo $(VAR1)

% make
echo 10 + 100
10 + 100

输出:

% make
echo 10 + 100
10 + 100

编辑: 已经有很好的答案了。我在Udemy的培训课程中遇到了这个概念,并且有很好的例子来说明。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接