CC=g++
CFLAGS=-c -Wall
LDFLAGS=
SOURCES=main.cpp hello.cpp factorial.cpp
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=hello
all: $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CC) $(LDFLAGS) $(OBJECTS) -o $@
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
$@
和 $<
究竟做了什么?
$@
是正在生成的目标的名称,$<
是第一个先决条件(通常是源文件)。您可以在 GNU Make manual 中找到所有这些特殊变量的列表。
例如,考虑以下声明:
all: library.cpp main.cpp
在这种情况下:
$@ 评估为所有
$< 计算为 library.cpp
$^ 计算为 library.cpp main.cpp
从 Managing Projects with GNU Make, 3rd Edition, p. 16(它在 GNU 自由文档许可证 下):
在匹配规则后,自动变量由 make 设置。它们提供对目标和先决条件列表中元素的访问,因此您不必显式指定任何文件名。它们对于避免代码重复非常有用,但在定义更通用的模式规则时至关重要。有七个“核心”自动变量: $@:代表目标的文件名。 $%:归档成员规范的文件名元素。 $<:第一个先决条件的文件名。 $?:比目标更新的所有先决条件的名称,以空格分隔。 $^:所有先决条件的文件名,以空格分隔。此列表已删除重复的文件名,因为对于大多数用途,例如编译、复制等,不需要重复。 $+:与 $^ 类似,这是由空格分隔的所有先决条件的名称,除了 $+ 包含重复项。该变量是为特定情况创建的,例如重复值有意义的链接器参数。 $*:目标文件名的词干。词干通常是没有后缀的文件名。不鼓励在模式规则之外使用它。此外,上述每个变量都有两个变体以与其他品牌兼容。一种变体仅返回值的目录部分。这通过将“D”附加到符号 $(@D)、$(
$@
和 $<
称为自动变量。变量 $@
表示目标的名称,$<
表示创建输出文件所需的第一个先决条件。
例如:
hello.o: hello.c hello.h
gcc -c $< -o $@
这里,hello.o
是输出文件。这就是 $@
扩展的内容。第一个依赖项是 hello.c
。这就是 $<
扩展的内容。
-c
标志生成 .o
文件;有关更详细的说明,请参见 man gcc
。 -o
指定要创建的输出文件。
有关详细信息,您可以阅读 this article on linoxide about Linux Makefiles。
此外,您可以检查 GNU make
manuals。这将使制作 Makefile 和调试它们变得更加容易。
如果运行此命令,它将输出 makefile 数据库:
make -p
$<
将扩展到 hello.c hello.h
(两者)。请说清楚。
$<
只是第一项。要包括所有,请使用 $^
。
$@
和 $<
是特殊的宏。
在哪里:
$@
是目标的文件名。
$<
是第一个依赖项的名称。
如果 main.cpp
、hello.cpp
、factorial.cpp
中的任何一项发生更改,Makefile 将构建 hello
可执行文件。实现该规范的最小可能 Makefile 可能是:
hello: main.cpp hello.cpp factorial.cpp
g++ -o hello main.cpp hello.cpp factorial.cpp
亲:非常容易阅读
缺点:维护噩梦,C++ 依赖项的重复
缺点:效率问题,我们重新编译所有 C++,即使只更改了一个
为了改进上述内容,我们只编译那些被编辑过的 C++ 文件。然后,我们只需将生成的目标文件链接在一起。
OBJECTS=main.o hello.o factorial.o
hello: $(OBJECTS)
g++ -o hello $(OBJECTS)
main.o: main.cpp
g++ -c main.cpp
hello.o: hello.cpp
g++ -c hello.cpp
factorial.o: factorial.cpp
g++ -c factorial.cpp
亲:修复效率问题
缺点:新的维护噩梦,目标文件规则的潜在错字
为了改进这一点,我们可以用一个 .cpp.o
规则替换所有对象文件规则:
OBJECTS=main.o hello.o factorial.o
hello: $(OBJECTS)
g++ -o hello $(OBJECTS)
.cpp.o:
g++ -c $< -o $@
亲:回到有一个简短的makefile,有点容易阅读
这里的 .cpp.o
规则定义了如何从 anyfile.cpp
构建 anyfile.o
。
$< 匹配第一个依赖项,在本例中为 anyfile.cpp
$@ 匹配目标,在本例中为 anyfile.o。
Makefile 中存在的其他更改是:
更容易将编译器从 g++ 更改为任何 C++ 编译器。
更容易更改编译器选项。
更容易更改链接器选项。
使更改 C++ 源文件和输出变得更容易。
添加了默认规则“全部”,作为快速检查以确保在尝试构建应用程序之前所有源文件都存在。
例如,如果您想编译源代码但在不同的目录中有对象:
你需要做:
gcc -c -o <obj/1.o> <srcs/1.c> <obj/2.o> <srcs/2.c> ...
但是对于大多数宏,结果将是所有对象后跟所有源,例如:
gcc -c -o <all OBJ path> <all SRC path>
所以这不会编译任何东西 ^^ 并且您将无法将对象文件放在不同的目录中:(
解决方案是使用这些特殊的宏
$@ $<
这将为 SRC (src/file.c) 中的每个 .c 文件生成一个 .o 文件 (obj/file.o)
$(OBJ):$(SRC)
gcc -c -o $@ $< $(HEADERS) $(FLAGS)
它的意思是 :
$@ = $(OBJ)
$< = $(SRC)
但是逐行替换所有 OBJ 行,然后是所有 SRC 行
$@
不一定最终必须是一个文件,它也可以是一个.PHONY
目标的名称。$@s
以生成诸如 name.os 之类的程序集输出吗?