1、make架构详解

make 是一个用于自动化构建和管理依赖关系的工具。它通过读取一个名为 Makefile 的文件,按照其中定义的规则来执行相应的操作。以下是一些基本的 make 执行规则和概念:

1. 目标(Target)

目标是 make 要创建或更新的文件,通常是可执行文件或对象文件。

2. 依赖(Dependencies)

依赖是目标所依赖的文件或其他目标。如果依赖文件发生变化,目标将被重新构建。

3. 命令(Commands)

命令是当目标需要更新时要执行的操作,通常是编译或链接的命令。命令必须以制表符(Tab)开始。

4. 基本结构

一个简单的 Makefile 示例:

makefile复制代码

target: dependencies command

5. 示例

假设有一个简单的 C 项目,有两个源文件 main.cutils.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.outils.o

  • 如果 main.outils.o 不存在或需要更新,make 将会依次执行对应规则来生成这两个对象文件。

  • 随后,make 将执行链接命令 $(CC) $(CFLAGS) -o app main.o utils.o,构建最终的可执行文件 app


2、gcc -o 详解

在使用 gcc 编译器时,-o 选项用于指定输出文件的名称。它允许用户定义生成的可执行文件或对象文件的名称,而不是使用默认名称。

常见用法

  1. 编译源代码为可执行文件

当你想要将一个 C 源文件编译为一个可执行文件时,可以使用 -o 选项来指定输出文件名。例如:

bash复制代码

gcc main.c -o my_program

在这个例子中,main.c 是源代码文件,my_program 是生成的可执行文件名。如果没有指定 -o 选项,默认情况下输出文件名将是 a.out(在 Unix/Linux 系统中)。

  1. 编译源代码为对象文件

如果你想将 C 源文件编译为对象文件(即 .o 文件),同样可以使用 -o 选项。例如:

bash复制代码

gcc -c main.c -o main.o

在这个例子中,-c 选项表示只编译,不进行链接。结果是生成 main.o 对象文件,而不是默认的 main.o

完整示例

假设你有两个源文件 main.cutils.c,并且希望生成一个名为 app 的可执行文件,你可以使用以下命令:

bash复制代码

gcc main.c utils.c -o app

这条命令将 main.cutils.c 编译和链接成一个名为 app 的可执行文件。

总结

  • -o 选项用于指定输出文件的名称。
  • 可以用于创建可执行文件或对象文件。
  • 如果不使用 -o,GCC 会默认为 a.out 作为可执行文件名,或者使用输入文件的基础名加上 .o 作为对象文件名。

3、make构建流程

  1. 源文件

通常以 .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

以下是构建过程的详细步骤和生成的文件顺序:

  1. 准备阶段:
  • 确保有源文件:main.cutils.c
  1. 编译阶段:
  • 当你运行 make 时,make 会读取 Makefile 并找到默认目标 app
  • 检查 app 的依赖项:main.outils.o
  1. 生成对象文件:
  • 如果 main.o 不存在或 main.c 最近更新,执行以下命令:

    bash复制代码

    gcc -Wall -c main.c

    这将生成 main.o 对象文件。

  • 接下来,如果 utils.o 不存在或 utils.c 最近更新,执行以下命令:

    bash复制代码

    gcc -Wall -c utils.c

    这将生成 utils.o 对象文件。

  1. 生成可执行文件:
  • 一旦所有对象文件都已生成,执行链接命令:

    bash复制代码

    gcc -Wall -o app main.o utils.o

    最终生成可执行文件 app

  1. 清理(可选):
  • 如果你想要删除生成的对象文件和可执行文件,可以运行:

    bash复制代码

    make clean

    这将执行 rm -f *.o app,清除所有生成的文件。

总结

在整个 make 构建过程中,通常包括以下文件及其生成顺序:

  1. 源文件(如 .c, .cpp
  2. 对象文件(如 .o
  3. 可执行文件(如 app

4、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 自动变量:

常用的自动变量

  1. $@: 表示规则中的目标文件名。例如,在链接阶段,$@ 会被替换为目标可执行文件的名称。

  2. $^: 表示规则中的所有依赖文件名,且没有重复项。通常在链接时使用,用于列出所有需要链接的对象文件。

  3. $<: 表示第一个依赖文件名。在编译规则中,这表示要编译的源文件。

  4. $*: 表示不带扩展名的目标文件名。通常用于模式匹配规则中,例如在生成 .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.outils.o)。

优势

使用自动变量的主要优点包括:

  • 减少冗余: 自动变量可以减少手动输入,使得 Makefile 更加简洁和易于维护。
  • 提高灵活性: 如果您更改了目标或依赖关系,自动变量将自动适应这些更改,而无需修改命令行。
  • 清晰性: 它们使得构建过程的逻辑更加清晰,更容易理解每个命令所做的事情。

总结

自动变量是 Makefile 中的一种强大工具,能帮助开发者简化构建过程,提高可读性和维护性。通过合理使用这些自动变量,您可以使得 Makefile 既灵活又高效

7、立即展开和延时展开:

Makefile 中,变量的展开方式主要分为两种:立即展开(即刻展开)和延迟展开(稍后展开)。这两种展开方式在目标和依赖关系中的使用以及命令中的应用有不同的目的和效果。我们来逐一解释它们的区别,并通过示例来说明。

变量的展开方式

  1. 立即展开:
  • 立即展开的变量使用 = 定义。在 Makefile 被读取时,这些变量的值会立即计算并替换。
  • 适用于定义目标和其依赖项,因为目标和依赖项在 make 开始处理规则时就需要确定。
  1. 延迟展开:
  • 延迟展开的变量使用 := 定义。这样,变量的值会在实际使用时计算,而不是在定义时。
  • 适用于命令中的变量,因为这些命令在目标被构建时执行,此时所需的最新信息是重要的。

示例

下面是一个简单的 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

分析

  1. 立即展开的变量:
  • SOURCE_FILESOBJECT_FILES 是使用立即展开定义的。在 Makefile 被解析时,OBJECT_FILES 会被计算为 main.o utils.o,无论在构建过程的任何时刻,其值都是固定的。
  1. 延迟展开的变量:
  • 在编译和链接阶段,$(CC)$(CFLAGS) 是延迟展开的。这意味着在执行命令时,会获取当前的 CCCFLAGS 的值,从而保证使用的是最新的配置。

为什么选择这种方式

  • 立即展开 用于目标和依赖项是因为这些信息在开始生成目标之前就已经确定,即使在多个不同的上下文中都能保持一致性。

  • 延迟展开 用于命令是因为在命令执行时,可能需要使用到最新的状态或环境设置,这样才能确保编译和链接使用的参数是正确的。

09-24 14:07