为什么 Make 更喜欢隐式规则而不是 Makefile 中列出的规则?

Why does Make prefer the implicit rule over the rule listed in the Makefile?

我想弄清楚为什么 Make 使用特定规则来编译我的代码。这是一个最小的例子:

当 运行 make 我得到以下信息:

$ make
gcc -O2 -Wno-implicit-function-declaration    main1.c   -o main1
/usr/bin/ld: /tmp/ccBsxlMV.o: in function `main':
main1.c:(.text.startup+0x11): undefined reference to `add'
collect2: error: ld returned 1 exit status
make: *** [<builtin>: main1] Error 1

我的意图是通过 %: %.o $(obj_lib) 表明每个目标都依赖于所有目标文件,因此目标的 .o 文件将与 add.o 链接。但是,我观察到的是 Make 正在使用隐式规则将 .c 文件编译为可执行文件,但由于 add.o 未链接到其中而失败。

为什么Make使用隐式规则而不是我定义的规则? 我怎样才能让 Make 使用我在那里指定的命令编译我的目标?

正在使用其他隐式规则,因为它们可以。 .c 文件存在,因此可用于构建可执行文件。

有多种方法可以解决此问题:

  1. 添加 $(obj_lib) 作为 $(targets) 的依赖项。这将导致 Make 确保库 .o 文件链接到主可执行文件中,从而解决该问题。但是,这仍然可以使用隐式规则,因此它没有解决问题的第二部分。

  2. 防止 Make 使用隐式规则。 这是我发现最适合我的用例的解决方案。这可以通过将以下内容添加到 Makefile 的末尾来完成。

    # Delete implicit rules that could be used to create the executable.
    %: %.c
    %: %.o
    # Indicate that Make doesn't need to delete any intermediate objects. (Optional)
    .SECONDARY:
    

这里是这个例子修改前后的演示:

=============================================================
$ ls
Makefile  add.c  main1.c  main2.c  make.log
=============================================================
$ cat Makefile 
CC := gcc
CFLAGS := -O2 -Wno-implicit-function-declaration

targets := main1 main2
obj_targets := $(patsubst %,%.o,$(targets))

src_lib := add.c
obj_lib := $(patsubst %.c,%.o,$(src_lib))

all: $(targets)
%: %.o $(obj_lib)
        $(CC) $(CFLAGS) -static -o $@ $^

# .PHONY: all
# # Delete implicit rules that could be used to create the executable.
# %: %.c
# %: %.o
# # Indicate that Make doesn't need to delete any intermediate objects.
# .SECONDARY:
=============================================================
$ make
gcc -O2 -Wno-implicit-function-declaration    main1.c   -o main1
/usr/bin/ld: /tmp/ccBsxlMV.o: in function `main':
main1.c:(.text.startup+0x11): undefined reference to `add'
collect2: error: ld returned 1 exit status
make: *** [<builtin>: main1] Error 1
=============================================================
$ code Makefile 
=============================================================
$ cat Makefile 
CC := gcc
CFLAGS := -O2 -Wno-implicit-function-declaration

targets := main1 main2
obj_targets := $(patsubst %,%.o,$(targets))

src_lib := add.c
obj_lib := $(patsubst %.c,%.o,$(src_lib))

all: $(targets)
%: %.o $(obj_lib)
        $(CC) $(CFLAGS) -static -o $@ $^

.PHONY: all
# Delete implicit rules that could be used to create the executable.
%: %.c
%: %.o
# Indicate that Make doesn't need to delete any intermediate objects.
.SECONDARY:
=============================================================
$ make
gcc -O2 -Wno-implicit-function-declaration   -c -o main1.o main1.c
gcc -O2 -Wno-implicit-function-declaration   -c -o add.o add.c
gcc -O2 -Wno-implicit-function-declaration -static -o main1 main1.o add.o
gcc -O2 -Wno-implicit-function-declaration   -c -o main2.o main2.c
gcc -O2 -Wno-implicit-function-declaration -static -o main2 main2.o add.o
=============================================================
$ 

原因是make总是使用最短的隐式规则链。所以,如果 make 可以找到一个规则 %.x -> %.y 和另一个规则 %.x -> %.z -> %.y,它总是会选择第一个。

Make 不会区别对待内置规则和用户定义规则,除了用户定义规则在内置规则之前先被搜索。

在您的情况下,您有一个内置规则知道如何从同名源文件创建二进制文件,以及您定义的规则知道如何从目标文件和另一个文件创建二进制文件从源文件构建目标文件的链式规则。所以make选择第一个。

您可以删除该内置规则:

%: %.c

就像您所做的那样(您不必删除 %.o : %.c 规则)。或者,您可以使用 static pattern rule,它创建一个明确的规则:

all: $(targets)

$(targets): %: %.o $(obj_lib)
        ...

因为这是一种显式规则,所以不会使用隐式规则(如模式规则)。