1、make架构详解
make
是一个用于自动化构建和管理依赖关系的工具。它通过读取一个名为 Makefile
的文件,按照其中定义的规则来执行相应的操作。以下是一些基本的 make
执行规则和概念:
1. 目标(Target)
目标是 make
要创建或更新的文件,通常是可执行文件或对象文件。
2. 依赖(Dependencies)
依赖是目标所依赖的文件或其他目标。如果依赖文件发生变化,目标将被重新构建。
3. 命令(Commands)
命令是当目标需要更新时要执行的操作,通常是编译或链接的命令。命令必须以制表符(Tab)开始。
4. 基本结构
一个简单的 Makefile
示例:
makefile复制代码
target: dependencies command
5. 示例
假设有一个简单的 C 项目,有两个源文件 main.c
和 utils.c
,并且希望生成一个可执行文件 app
,可以写一个如下的 Makefile
:
makefile复制代码
CC = gcc
CFLAGS = -Wall
app: main.o utils.o
$(CC) $(CFLAGS) -o app main.o utils.o
main.o: main.c
$(CC) $(CFLAGS) -c main.c
utils.o: utils.c
$(CC) $(CFLAGS) -c utils.c
clean:
rm -f *.o app
6. 使用说明
- 构建:在终端中运行
make
命令,默认会构建第一个目标(在这个例子中是app
)。 - 清理:运行
make clean
可以删除所有生成的目标文件和可执行文件。
7. 特殊变量
CC
: 指定编译器。CFLAGS
: 指定编译选项。
8. 内置规则
make
有许多内置规则,可以简化常见任务,例如自动处理 .c
到 .o
的编译过程。
9. 伪目标
如上例中的 clean
,不是文件名,而是一个伪目标,用于执行特定命令而不产生文件。
总结
-
-o
选项用于指定输出文件的名称。 -
可以用于创建可执行文件或对象文件。
-
如果不使用
-o
,GCC 会默认为a.out
作为可执行文件名,或者使用输入文件的基础名加上.o
作为对象文件名。 -
首先,当你运行
make app
时,make
会查找app
目标,并发现它依赖于main.o
和utils.o
。 -
如果
main.o
和utils.o
不存在或需要更新,make
将会依次执行对应规则来生成这两个对象文件。 -
随后,
make
将执行链接命令$(CC) $(CFLAGS) -o app main.o utils.o
,构建最终的可执行文件app
。
2、gcc -o 详解
在使用 gcc
编译器时,-o
选项用于指定输出文件的名称。它允许用户定义生成的可执行文件或对象文件的名称,而不是使用默认名称。
常见用法
- 编译源代码为可执行文件
当你想要将一个 C 源文件编译为一个可执行文件时,可以使用 -o
选项来指定输出文件名。例如:
bash复制代码
gcc main.c -o my_program
在这个例子中,main.c
是源代码文件,my_program
是生成的可执行文件名。如果没有指定 -o
选项,默认情况下输出文件名将是 a.out
(在 Unix/Linux 系统中)。
- 编译源代码为对象文件
如果你想将 C 源文件编译为对象文件(即 .o
文件),同样可以使用 -o
选项。例如:
bash复制代码
gcc -c main.c -o main.o
在这个例子中,-c
选项表示只编译,不进行链接。结果是生成 main.o
对象文件,而不是默认的 main.o
。
完整示例
假设你有两个源文件 main.c
和 utils.c
,并且希望生成一个名为 app
的可执行文件,你可以使用以下命令:
bash复制代码
gcc main.c utils.c -o app
这条命令将 main.c
和 utils.c
编译和链接成一个名为 app
的可执行文件。
总结
-o
选项用于指定输出文件的名称。- 可以用于创建可执行文件或对象文件。
- 如果不使用
-o
,GCC 会默认为a.out
作为可执行文件名,或者使用输入文件的基础名加上.o
作为对象文件名。
3、make构建流程
构建流程
使用 make 工具进行构建时,通常会生成几个不同类型的文件,包括源文件、对象文件和可执行文件等。以下是一个典型的构建流程,以及在这个过程中生成的各种文件及其生成顺序。
- 源文件
通常以 .c
、.cpp
或其他语言相关的扩展名存在。这些是最初的代码文件,例如:
main.c
utils.c
2. 对象文件
在编译过程中,每个源文件会被编译成一个对象文件(通常以 .o
作为扩展名)。这些文件包含机器代码,但不能直接执行。例如:
main.o
utils.o
3. 可执行文件
链接所有对象文件后,最终生成的可执行文件。例如:
app
4. Makefile
Makefile
是描述如何构建项目的文件,它定义了目标、依赖关系和命令。尽管不是由 make
直接生成的文件,但它是整个过程的核心。
构建过程顺序
假设有如下的 Makefile
:
makefile复制代码
CC = gcc CFLAGS = -Wall app: main.o utils.o $(CC) $(CFLAGS) -o app main.o utils.o main.o: main.c $(CC) $(CFLAGS) -c main.c utils.o: utils.c $(CC) $(CFLAGS) -c utils.c clean: rm -f *.o app
以下是构建过程的详细步骤和生成的文件顺序:
- 准备阶段:
- 确保有源文件:
main.c
和utils.c
。
- 编译阶段:
- 当你运行
make
时,make
会读取Makefile
并找到默认目标app
。 - 检查
app
的依赖项:main.o
和utils.o
。
- 生成对象文件:
-
如果
main.o
不存在或main.c
最近更新,执行以下命令:bash复制代码
gcc -Wall -c main.c
这将生成
main.o
对象文件。 -
接下来,如果
utils.o
不存在或utils.c
最近更新,执行以下命令:bash复制代码
gcc -Wall -c utils.c
这将生成
utils.o
对象文件。
- 生成可执行文件:
-
一旦所有对象文件都已生成,执行链接命令:
bash复制代码
gcc -Wall -o app main.o utils.o
最终生成可执行文件
app
。
- 清理(可选):
-
如果你想要删除生成的对象文件和可执行文件,可以运行:
bash复制代码
make clean
这将执行
rm -f *.o app
,清除所有生成的文件。
总结
在整个 make
构建过程中,通常包括以下文件及其生成顺序:
- 源文件(如
.c
,.cpp
) - 对象文件(如
.o
) - 可执行文件(如
app
)
4、clean使用
随着项目的演变,依赖项可能会发生变化。旧的编译文件可能无法正确反映新的依赖关系。使用 make clean 可以帮助确保所有的文件都是基于目前的依赖关系和内容重新生成的。
5、隐式规则和模式规则:
在 make
中,规则用于定义如何构建目标文件。规则可以分为隐式规则和显式规则。
1. 显式规则
显式规则是用户明确指定的规则,通常包含目标、依赖关系和构建命令。以下是一个示例:
# 显式规则示例
target: dependencies
command
示例
# 这是一个将源文件 hello.c 编译成可执行文件 hello 的显式规则
hello: hello.o main.o
gcc -o hello hello.o main.o
# 依赖规则,表示如何生成 hello.o
hello.o: hello.c
gcc -c hello.c
# 依赖规则,表示如何生成 main.o
main.o: main.c
gcc -c main.c
# 清理目标
clean:
rm -f hello *.o
2. 隐式规则
隐式规则是 make
自动应用的规则,允许用户在不显式指定的情况下创建常见文件类型。例如,make
知道如何从 .c
文件生成 .o
文件。
示例
下面的 Makefile
演示了隐式规则的使用:
CC = gcc
# 使用模式匹配规则的 Makefile
# 设置编译器和编译选项
CC = gcc
CFLAGS = -Wall
# 定义最终目标
hello: $(patsubst %.c, %.o, $(wildcard *.c))
# 模式匹配规则,用于从 .c 文件生成 .o 文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理目标
clean:
rm -f hello *.o
在这个例子中,make
默认知道如何从 *.c
文件创建 *.o
文件,因此不需要我们为每个对象文件编写显式规则。
总结
- 显式规则:用户明确指定,适合复杂的构建需求。
- 隐式规则:由
make
自动处理,适合常见的编译任务,简化了Makefile
的书写。
6 自动变量:
常用的自动变量
-
$@
: 表示规则中的目标文件名。例如,在链接阶段,$@
会被替换为目标可执行文件的名称。 -
$^
: 表示规则中的所有依赖文件名,且没有重复项。通常在链接时使用,用于列出所有需要链接的对象文件。 -
$<
: 表示第一个依赖文件名。在编译规则中,这表示要编译的源文件。 -
$*
: 表示不带扩展名的目标文件名。通常用于模式匹配规则中,例如在生成.o
文件时,可以用来生成相应的文件名。
示例
考虑下面的 Makefile
片段:
makefile复制代码
# 编译规则示例
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
main: main.o utils.o
$(CC) $(CFLAGS) -o $@ $^
在这个示例中:
- 当
make
处理main.o: main.c
规则时,$<
将会被替换为main.c
,而$@
则是main.o
。 - 在链接目标
main
的规则中,$@
将被替换为main
,而$^
则包含了所有依赖的对象文件(例如main.o
和utils.o
)。
优势
使用自动变量的主要优点包括:
- 减少冗余: 自动变量可以减少手动输入,使得
Makefile
更加简洁和易于维护。 - 提高灵活性: 如果您更改了目标或依赖关系,自动变量将自动适应这些更改,而无需修改命令行。
- 清晰性: 它们使得构建过程的逻辑更加清晰,更容易理解每个命令所做的事情。
总结
自动变量是 Makefile
中的一种强大工具,能帮助开发者简化构建过程,提高可读性和维护性。通过合理使用这些自动变量,您可以使得 Makefile
既灵活又高效
7、立即展开和延时展开:
在 Makefile
中,变量的展开方式主要分为两种:立即展开(即刻展开)和延迟展开(稍后展开)。这两种展开方式在目标和依赖关系中的使用以及命令中的应用有不同的目的和效果。我们来逐一解释它们的区别,并通过示例来说明。
变量的展开方式
- 立即展开:
- 立即展开的变量使用
=
定义。在Makefile
被读取时,这些变量的值会立即计算并替换。 - 适用于定义目标和其依赖项,因为目标和依赖项在 make 开始处理规则时就需要确定。
- 延迟展开:
- 延迟展开的变量使用
:=
定义。这样,变量的值会在实际使用时计算,而不是在定义时。 - 适用于命令中的变量,因为这些命令在目标被构建时执行,此时所需的最新信息是重要的。
示例
下面是一个简单的 Makefile
示例,通过这个例子可以看到如何使用立即展开与延迟展开变量:
# 使用立即展开的变量
SOURCE_FILES = main.c utils.c
OBJECT_FILES = $(SOURCE_FILES:.c=.o) # 立即展开,计算出对象文件列表
# 使用延迟展开的变量
CC = gcc
CFLAGS = -Wall
# 最终目标
TARGET = program
all: $(TARGET)
# 链接目标
$(TARGET): $(OBJECT_FILES)
$(CC) $(CFLAGS) -o $@ $(OBJECT_FILES)
# 编译规则,使用延迟展开的命令
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGET) $(OBJECT_FILES)
.PHONY: all clean
分析
- 立即展开的变量:
SOURCE_FILES
和OBJECT_FILES
是使用立即展开定义的。在Makefile
被解析时,OBJECT_FILES
会被计算为main.o utils.o
,无论在构建过程的任何时刻,其值都是固定的。
- 延迟展开的变量:
- 在编译和链接阶段,
$(CC)
和$(CFLAGS)
是延迟展开的。这意味着在执行命令时,会获取当前的CC
和CFLAGS
的值,从而保证使用的是最新的配置。
为什么选择这种方式
-
立即展开 用于目标和依赖项是因为这些信息在开始生成目标之前就已经确定,即使在多个不同的上下文中都能保持一致性。
-
延迟展开 用于命令是因为在命令执行时,可能需要使用到最新的状态或环境设置,这样才能确保编译和链接使用的参数是正确的。