makefile学习笔记(一)(make架构详解、gcc -o 详解、make构建流程、clean使用、隐式规则和模式规则、自动变量、立即展开和延时展开)

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构建流程

构建流程
使用 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使用

随着项目的演变,依赖项可能会发生变化。旧的编译文件可能无法正确反映新的依赖关系。使用 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 自动变量:

常用的自动变量

  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 的值,从而保证使用的是最新的配置。

为什么选择这种方式

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

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

七、静态模式 静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法: ;: ;: ; ; ... targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。 target-parrtern是指明了targets的模式,也就是的目标集模式。 prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。 这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们的;定义成“%.o”,意 思是我们的;集合中都是以“.o”结尾的,而如果我们的;定义成“%. c”,意思是对;所形成的目标集进行二次定义,其计算方法是,取;模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。 所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你可以使用反斜杠“\”进行转义,来标明真实的“%”字符。 看一个例子: objects = foo.o bar.o all: $(objects) $(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ 上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。而命令中的“$<”和“$@”则是自动变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则: foo.o : foo.c $(CC) -c $(CFLAGS) foo.c -o foo.o bar.o : bar.c $(CC) -c $(CFLAGS) bar.c -o bar.o 试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。再看一个例子: files = foo.elc bar.o lose.o $(filter %.o,$(files)): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ $(filter %.elc,$(files)): %.elc: %.el emacs -f batch-byte-compile $< $(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。 八、自动生成依赖性 在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句“#include "defs.h"”,那么我们的依赖关系应该是: main.o : main.c defs.h
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

week_泽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值