这是我的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

问题是当我try 运行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-gt;foo.o->;foo构建程序,并且它也可以使用foo.c-gt;foo构建程序,则将使用后一条链.

这就是您在这里看到的:make使用的是内置的隐式规则,而不是您的隐式规则提供的较长的规则集.

你有很多 Select .最简单的方法是禁用内置的隐式规则;如果您有GNU Make 4.0或更高版本,这将起作用:

MAKEFLAGS += -r

如果你不这样做,这也会起作用,尽管它不是很好:

.SUFFIXES:

(您可以在GNU make手册中找到关于.SUFFIXES以及它的用途的信息).

但是,总的来说,在这样的模式规则中添加"额外的"先决条件并不是一个好主意:

%: %.o tap.o
         gcc -o $@ $^

模式规则是template,而不是promise .因此,如果出于某种原因没有 Select 该规则,则先决条件tap.o将不适用.

更好的做法是显式添加先决条件,如下所示:

%: %.o
         gcc -o $@ $^

$(EXES): tap.o

现在你know那些可执行文件都必须有tap.o.

另一种 Select 是使用静态模式规则.一般来说,"Match-Anything"规则(目标为%的模式规则)不利于Make文件中的性能.因此,最好声明一个静态模式规则,如下所示:

$(EXES): %: %.o tap.o
         gcc -o $@ $^

尽管名称令人困惑,但静态模式规则根本不是隐式规则:这是为$(EXES)中的每个目标创建显式规则的缩写.

C++相关问答推荐

C sscanf没有捕获第二个参数

需要大整数和浮点数.使用long long int和long double

手动矢量化性能差异较大

C编译器是否遵循restrict的正式定义?

C lang:当我try 将3个或更多元素写入数组时,出现总线错误

对重叠字符串使用MemMove

在句子中转换单词的问题

tick.q中的Kdb+键控表语法

为什么函数是按照定义的顺序执行的,而不是按照从avr-c中的int main()调用的顺序执行的?

不同出处的指针可以相等吗?

用C++初始化局部数组变量

传递给函数的 struct 中的数组

意外的C并集结果

Linux Posix消息队列

将数组中的所有元素初始化为 struct 中的相同值

是什么阻止编译器优化手写的 memcmp()?

如何在 C 中的 Postgres 函数的表中 for 循环

Clang 是否为内联汇编生成了错误的代码?

C 预处理器中的标记分隔符列表

使用 SDL2 的 C 程序中的内存泄漏