我正在学习Zed Shaw的《学习C语言的困难之路》课程,但在第2课中,作者在视频和书籍中只使用make ex1
来编译他的C代码,而我需要运行gcc -Wall -g ex1.c -o ex1
。这是我的Makefile,似乎第一部分应该让make
命令工作来编译,但是"make"命令并没有编译它。
CFLAGs=-Wall -g
clean:
rm -f ex1.exe
all:
gcc -Wall -g ex1.c -o ex1.exe
默认目标是第一个目标,因此请更改它们的顺序,使all
成为默认值。
有关目标的详细信息,请参阅目标文档:
默认情况下,目标是makefile中的第一个目标(不包括以句点开头的目标)。因此,makefile通常是这样编写的,即第一个目标用于编译它们描述的整个程序或程序集。如果makefile中的第一条规则有多个目标,则只有规则中的第一个目标变成默认目标,而不是整个列表。您可以通过使用.DEFAULT_GOAL
变量来管理默认目标的选择。
ex1.exe
应该成为 all
的先决条件 (all: ex1.exe
而不仅仅是 all:
),否则 make
无法检测它是否是最新的。另外,可以选择添加 .PHONY: all
来避免与一个名为 all
的文件混淆。 - anatolygmake
。make all
仅适用于ex1,而我想只使用make。 - Jerry Dex1.exe
作为任何东西的先决条件,因为只需要在命令行中使用all
目标来构建ex1.exe
。虽然我不太喜欢Makefile
的编写方式,但只需输入make all
即可生成可执行文件。它没有任何依赖信息。 - Luis Colorado您的Makefile
没有包含形式为规则的依赖关系信息。 规则是以左列(在第0列)开头并在目标(要构建的对象)和必备条件(提供给规则的对象)之间具有:
分隔符的行。
Make会链接规则,因此在其前提条件构建之前不能构建任何目标,因此一个好的起点是:
ex1.exe: ex1.o
这意味着Makefile
中的第一个目标ex1.exe
依赖于编译后的对象ex1.o
......但是如何实现呢?您可以在下面添加命令,但是要在命令行前插入一个制表符。
[tab char]gcc -Wall -g ex1.o -o ex1.exe
首先,看一下这个命令如何将 ex1.o
作为输入传递给 gcc
并通过选项 -o
生成文件 ex1.exe
。
所有这些的意义是,只要你缺少 ex1.exe
或者它在前提条件(ex1.o
)之前被修改(从文件的修改日期中可以看出),那么该命令就会被使用。
现在,我们如何编译对象文件呢?(在您的 Makefile 中,它是直接生成的)
ex1.o: ex1.c
$(CC) $(CFLAGS) -c -o $@ $<
这行代码需要更多的解释(尽管如果您不使用它,make
程序会自动提供)。它使用替换变量:$(CC)
扩展为默认的C编译器(通常是cc
,但您可以更改它)。$(CFLAGS)
是您在Makefile
顶部定义的变量,并扩展为通常用于编译程序的编译器选项。 $@
由目标文件(在本例中为ex1.o
文件)扩展,而$<
则扩展为规则右部的左侧先决条件。 这个规则,将C文件编译成目标文件非常常见,由make
自动生成,更多的规则可以在make
高级用法中创建(由于所需空间的数量,我不会在此输入),只是说它可以写成这样:
.c.o:
$(CC) $(CFLAGS) -c -o $@ $<
这意味着:通过使用下面的命令行,可以从具有相同名称但扩展名为.c
的文件创建任何扩展名为.o
的文件。 这使每个.o
文件自动依赖于具有相同名称但扩展名为.c
的文件,并预加载在make
中。
因此,您最简单的Makefile
可能是:
CFLAGS=-Wall -g
src=ex1.c
ex1.exe: $(src)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(src)
正如您所看到的,我使用了一个变量src
来描述用于编译我的程序的C源文件。只有当您修改任何一个前提条件(现在只有一个,在ex1.c
上,但可能会更多)时,依赖关系才会被发出。由于我正在进行编译和链接的同一编译,因此我需要传递编译标志和链接标志(此处未使用,您可以删除对$(LDFLAGS)
的引用)。实际上,您可以这样写:
ex1.exe: ex1.c
cc -g -Wall -o ex1.exe ex1.c
clean:
rm -f ex1.exe
所以,默认操作是创建 ex1.exe
,它依赖于 ex1.c
(所以只有当 ex1.c
被修改时,ex1.exe
才会被重新编译)。要编译它,只需执行以下命令:
make ex1.exe
或者
make
这将使用Makefile
的默认第一规则。
clean
是一个虚拟目标(没有叫做clean
的文件需要生成),所以当您使用它时,它将执行规则中定义的命令,从而删除指定的文件或目录。
make clean
它的相关命令总是被执行。它让make
删除您的目标文件(因为它会删除所有由make生成的文件),所以下次运行make
时,它将通过进行必要的编译来重新创建所有文件。
对于简单的程序,例如几个源代码.c
文件和一些.h
文件,每个文件都进行单独编译(使用编译中的-c
选项)和链接,我通常使用此方法(定义每个目标程序所需的对象文件不同变量中)。
# makefile for programs foo and bar.
targets = foo bar
TOCLEAN = $(TARGETS) # this variable stores what should be erased on clean target. Here we store the programs to erase.
foo_objs = a.o b.o c.o
TOCLEAN += $(foo_objs) # here, we add the objects of foo
foo: $(foo_objs)
$(CC) $(LDFLAGS) -o $@ $(foo_objs)
bar_objs = d.o e.o f.o
bar_libs = -lm -lintl -lX
TOCLEAN += $(bar_objs) # here we add the objects of bar
bar: $(bar_objs)
$(CC) $(LDFLAGS) -o $@ $(bar_objs) $(bar_libs)
clean:
rm -f $(TOCLEAN)
a.o b.o c.o: a.h b.h c.h globals.h
b.o c.o d.o e.o f.o: my_defs.h
正如您所见,对头文件的依赖关系是针对编译后的目标文件而不是.c
源文件...源文件构建了依赖关系,但在修改包含的文件时必须重新创建目标文件,而不是包含.c
文件。
$(CC) $(LDFLAGS)
而不是标准的 $(LINK.o)
呢?(这可以防止针对目标的 LINK.o = LINK.cc
用于链接 C++ 对象,例如)。同样,使用 $(RM)
而不是 rm -f
是标准做法,尽管需要覆盖它的情况较少。 - Toby Speightbar: LDLIBS += -lm -lintl -lX
,但这可能只是个人偏好? - Toby Speightmake
的LDLIBS
全局变量不允许您为每个可执行文件指定不同的库。您可以将其用于通用项目库,然后为每个可执行文件设置特定的程序库集。这与编译标志一样重要,因为在make
中通常不会默认包含链接的自动规则,但在编译时很重要。正如您所看到的,两个程序使用不同的对象和库。 - Luis ColoradoLDLIBS
。 - Toby Speightmake
的扩展功能。正如你所看到的,我最注重不使用GNU make的扩展功能,并使用非首选的旧符号“ .c.o:”目标。 - Luis ColoradoCFLAGS = -Wall -g
all: ex1
clean:
$(RM) ex1
.PHONY: all clean
我在开头修正了我的CFLAGS
定义,并在all
和clean
的末尾添加了.PHONY
,这样make
就知道不要为这些文件生成目标。
ex1
作为all
的前提条件,则在使用rm -f
命令时,请使用相同的ex1
作为参数。 - Luis Colorado对于一个非常简单的 Makefile,我会这样做:
qsorttest: qsorttest.c
gcc -Wall -g qsorttest.c -o qsorttest -lm
clean:
rm -f qsorttest
“qsorttest.c”是我的源文件名称,我创建的可执行文件名为“qsorttest”。请确保“program name: source file”和“clean:”下面的行已经缩进,并且没有使用空格键移动。我曾经为此苦恼了一段时间,直到通过谷歌找到解决方法。
我将其保存为“Makefile”,与程序的其他文件放在同一个目录下,这样每次都可以正常工作。如果需要,我只需更改源文件和程序名称,并添加额外的参数以链接库。
CFLAGs
,小写的s
? - Joseph QuinseyCFLAG[sS]
(无论大小写如何),那么它的原因是什么?在该Makefile中没有依赖信息,因此不会选择任何自动规则,并且不会使用CFLAGS
变量。 - Luis Colorado