这是我的 Makefile,目标只是编译一堆测试文件并将它们与另一个名为
tap.c
的文件链接,其中包含我的测试函数。
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
EXES = $(filter-out tap,$(basename $(SRCS)))
all: $(EXES)
%: %.o tap.o
gcc -o $@ $^
%.o: %.c
gcc -c $<
tap.o: tap.c tap.h
gcc -c $<
clean:
rm -f $(OBJS)
fclean: clean
rm -f $(EXES)
这是非常基本的。
.
├── isalnum.c
├── isalpha.c
├── isascii.c
├── isdigit.c
├── isprint.c
├── Makefile
├── tap.c
└── tap.h
1 directory, 8 files
问题是当我尝试运行
make
时出现链接器错误。
cc isalnum.c -o isalnum
/usr/bin/ld: /tmp/ccnuuKzZ.o: in function `main':
isalnum.c:(.text+0x14): undefined reference to `tap_plan'
/usr/bin/ld: isalnum.c:(.text+0x4a): undefined reference to `ok_at_loc'
/usr/bin/ld: isalnum.c:(.text+0x80): undefined reference to `ok_at_loc'
/usr/bin/ld: isalnum.c:(.text+0xb8): undefined reference to `ok_at_loc'
/usr/bin/ld: isalnum.c:(.text+0xf0): undefined reference to `ok_at_loc'
/usr/bin/ld: isalnum.c:(.text+0x128): undefined reference to `ok_at_loc'
/usr/bin/ld: /tmp/ccnuuKzZ.o:isalnum.c:(.text+0x160): more undefined references to `ok_at_loc' follow
/usr/bin/ld: /tmp/ccnuuKzZ.o: in function `main':
isalnum.c:(.text+0x3d9): undefined reference to `exit_status'
collect2: error: ld returned 1 exit status
make: *** [<builtin>: isalnum] Error 1
我的
tap.c
文件包含所有这些符号,并且构建目标的规则似乎被绕过,因为执行的 cc
命令与我在 Makefile 中定义的规则不同。
果然,经过一番研究,我发现当我使用
make -r
时,一切似乎都正常工作。
gcc -c tap.c
gcc -c isalnum.c
gcc -o isalnum isalnum.o tap.o
gcc -c isalpha.c
gcc -o isalpha isalpha.o tap.o
gcc -c isascii.c
gcc -o isascii isascii.o tap.o
gcc -c isdigit.c
gcc -o isdigit isdigit.o tap.o
gcc -c isprint.c
gcc -o isprint isprint.o tap.o
rm isascii.o isprint.o isalnum.o isdigit.o isalpha.o
这里发生了什么?请告诉我我的工作流程出了什么问题,我肯定遗漏了一些关于 Makefile、链接和编译的重要内容。我希望我的初始 Makefile 能够毫无问题地工作。
GNU Make 有许多内置规则。这些规则之一知道如何直接从源文件构建可执行文件。
很常见的是,有许多不同的隐式规则可用于创建一个目标:例如,可以从 C 文件、C++ 文件等编译
%.o
。因此,GNU Make 也有一套规则来决定检查和应用隐式规则的顺序。
其中一条规则规定,如果有两种方法链接规则来设定目标,则较短的链获胜。因此,如果 make 可以使用
foo.c
-> foo.o
-> foo
构建程序,并且也可以使用 foo.c
-> foo
构建程序,那么将使用后一个链。
这就是您在这里看到的:make 使用内置的隐式规则,而不是隐式规则提供的较长规则集。
你有很多选择。最简单的一种是禁用内置的隐式规则;如果您有 GNU Make 4.0 或更高版本,这将起作用:
MAKEFLAGS += -r
如果你不这样做,这也可以工作,尽管它不是那么好:
.SUFFIXES:
(您可以在 GNU Make 手册中找到有关
.SUFFIXES
及其作用的信息)。
但是,一般来说,向模式规则添加“额外”先决条件是一个坏主意,如下所示:
%: %.o tap.o
gcc -o $@ $^
模式规则是一个模板而不是承诺。因此,如果由于某种原因未选择该规则,则先决条件
tap.o
将不适用。
更好的是明确添加先决条件,如下所示:
%: %.o
gcc -o $@ $^
$(EXES): tap.o
现在您知道那些可执行文件都必须有
tap.o
。
另一种选择是使用静态模式规则。一般来说,“匹配任何内容”规则(目标为
%
的模式规则)对 makefile 的性能不利。因此,最好声明一个静态模式规则,如下所示:
$(EXES): %: %.o tap.o
gcc -o $@ $^
虽然名称令人困惑,但静态模式规则根本不是隐式规则:这是为
$(EXES)
中的每个目标创建显式规则的简写。