为什么在更改源文件时 make 不重建?

Why is make not rebuilding when source files are changed?

我目前正在尝试 CS50 课程中的问题集 4(拼写)。这是我们有多个头文件和多个源文件的第一个问题集,所以他们给了我们一个 Makefile 来使用,将每个 .c 文件编译成 .o,然后 link 个 .o 文件形成编译二进制。

这是生成文件

speller:
    clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -c -o speller.o speller.c
    clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -c -o dictionary.o dictionary.c
    clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -o speller speller.o dictionary.o

ls 的输出:

dictionaries  dictionary.h  keys      speller    speller.o dictionary.c  dictionary.o  Makefile  speller.c  texts

当我 运行 第一次 make 时,编译拼写没有问题。但是,当我在 dictionary.c 中进行更改并保存它时(特别是,我故意搞砸了我对 printasdasdsa() 的所有 printf() 调用,是的,你明白了)并且我 运行 make,它一直在说make: 'speller' is up to date,即使我更改了 dictionary.c 的源代码,也只是拒绝重建。

知道我构建拼写器的方式有什么问题吗?我的 makefile 有问题吗?

我知道有一种方法可以通过传递“-B”标志来强制 make 重建,但是每当您更改代码时总是这样做是惯例吗?

这是任务:https://docs.cs50.net/2019/x/psets/4/speller/hashtable/speller.html

Make 仅在目标不存在或目标早于其依赖项之一时才重建目标。在您的情况下,您有一个没有依赖项的目标 speller。第一次 运行 它时,进行检查,但没有找到它,因此它会构建它。下次构建时,它会检查文件是否存在,并且由于它没有任何依赖性,因此不会重建。你会想做这样的事情:

speller:  speller.c dictionary.c
    clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -c -o speller.o speller.c
    clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -c -o dictionary.o dictionary.c
    clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -o speller speller.o dictionary.o

或者,更好的是:

speller: speller.o dictionary.o
    clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -o speller speller.o dictionary.o

speller.o: speller.c
    clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -c -o speller.o speller.c

dictionary.o: dictionary.c
    clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -c -o dictionary.o dictionary.c

除非 .c 文件更改,否则不会重建 .o 文件,除非重建其中一个 .o 文件,否则不会重建应用程序。请注意,这两个 都不处理任何 header 文件。如果您的 .c 文件包含任何本地 header,则还需要将它们添加到依赖项中。

@HardcoreHenry 在他的回答中很好地解释了 make 的行为(不要接受这个)。然而,我想指出,make 在构建软件方面具有相当多的内置智能,以至于它可以在根本没有任何 Makefile 的情况下进行相对简单的构建。此外,当您编写 Makefile 时,通常认为减少重复是一种很好的风格。

因此,我建议将其作为更好的替代方案:

CC = clang
CFLAGS = -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 \
  -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare       \
  -Wno-unused-parameter -Wno-unused-variable -Wshadow

speller: speller.o dictionary.o
    $(CC) -o $@ $(CFLAGS) speller.o dictionary.o

这依赖于 make 了解如何从 C 源文件构建目标文件(它确实如此)并使用 C 编译器以及 CCCFLAGS 指定的标志当它这样做时的变量(它会)。它还使用特殊变量 $@,在规则的配方中,它扩展为规则目标的名称。 make 的某些版本提供了更多的机会来解决这个问题。

除其他事项外,请注意编译器和构建标志是如何在顶部附近分别指定一次的。现在,如果您想更改这些内容,可以在一个容易找到的地方进行。

您需要添加依赖项。使用GNU make时,小项目可以跳过.o这一步,整体编译程序

speller:  speller.c dictionary.c
         ${CLANG} ${CFLAGS} ${LDFLAGS} $(filter %.c,$^) -o $@ ${LIBS}