Linux操作系统从入门到实战(十)Linux开发工具(下)make/Makefile的推导过程与扩展语法

发布于:2025-07-16 ⋅ 阅读:(19) ⋅ 点赞:(0)


前言

  • 前面的博客里我们讲解了Linux开发工具自动化构建-make/Makefile里的基础知识
  • 接下来我们继续讲解Linux开发工具自动化构建-make/Makefile里的细节,make/Makefile的推导过程与扩展语法

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482


一、 make/Makefile的推导过程

1. 先看一个完整的Makefile示例

假设我们有一个名为myproc的程序,对应的Makefile内容如下

在这里插入图片描述

在这里插入图片描述

# 最终目标:生成可执行文件myproc,依赖于中间文件myproc.o
myproc: myproc.o
    gcc myproc.o -o myproc  # 通过链接myproc.o生成可执行文件myproc

# 中间目标:生成目标文件myproc.o,依赖于汇编文件myproc.s
myproc.o: myproc.s
    gcc -c myproc.s -o myproc.o  # 将汇编文件myproc.s编译为目标文件myproc.o

# 中间目标:生成汇编文件myproc.s,依赖于预处理文件myproc.i
myproc.s: myproc.i
    gcc -S myproc.i -o myproc.s  # 将预处理文件myproc.i转换为汇编文件myproc.s

# 中间目标:生成预处理文件myproc.i,依赖于源文件myproc.c
myproc.i: myproc.c
    gcc -E myproc.c -o myproc.i  # 对源文件myproc.c进行预处理,生成myproc.i

# 伪目标:清理所有编译过程中产生的中间文件和可执行文件
.PHONY: clean
clean:
    rm -f *.i *.s *.o myproc  # 删除所有.i、.s、.o文件及myproc

执行make命令的输出

  • 当我们在终端输入make后,会看到如下执行过程:
$ make
gcc -E myproc.c -o myproc.i  # 第一步:预处理
gcc -S myproc.i -o myproc.s  # 第二步:生成汇编
gcc -c myproc.s -o myproc.o  # 第三步:生成目标文件
gcc myproc.o -o myproc       # 第四步:链接生成可执行文件

在这里插入图片描述

整个过程就像“剥洋葱”——从最终目标出发,逐层拆解依赖,直到找到最原始的源文件,再反向执行编译命令。

  • 下面我们详细解释make是如何一步步完成这个过程的。

2. make的工作流程

在默认情况下(即直接输入make命令),工具的执行逻辑可以拆解为以下8个核心步骤:

(1)寻找Makefile文件

  • make首先会在当前目录下搜索名为Makefilemakefile的文件(注意大小写敏感,推荐统一使用Makefile)。
  • 如果找不到这两个文件,会直接报错“没有规则可制作目标”

在这里插入图片描述

(2)确定最终目标

找到Makefile后,make会将文件中第一个目标作为“最终目标”。

  • 在上面的例子中,第一个目标是myproc(可执行文件)。
  • 因此make的最终任务就是生成myproc

在这里插入图片描述

(3) 检查最终目标是否需要更新

确定最终目标后,make会通过两个条件判断是否需要生成/更新myproc

  • myproc不存在:直接执行后续命令生成它;
  • myproc已存在:比较myproc和它的依赖文件myproc.o修改时间
  • 如果myproc.o的修改时间比myproc晚(即myproc.o被更新过),则需要重新生成myproc
  • 反之,若myprocmyproc.o新,说明myproc已是最新,无需操作。

小技巧:可以用touch 文件名命令手动更新文件的修改时间(比如touch myproc.o),测试make是否会重新执行命令。

在这里插入图片描述

(4) 从最终目标到中间文件

在这里插入图片描述
如果myproc需要更新(或不存在),make会检查它的依赖myproc.o

  • myproc.o不存在:在Makefile中寻找以myproc.o为目标的规则(即myproc.o: myproc.s这一行),然后根据规则生成myproc.o
  • myproc.o已存在:同样比较myproc.o和它的依赖myproc.s的修改时间,判断是否需要重新生成myproc.o

这个过程会逐层递归

  • 检查myproc.s是否存在/需要更新 → 依赖myproc.i
  • 检查myproc.i是否存在/需要更新 → 依赖myproc.c(源代码文件)。

直到找到最底层的依赖myproc.c——这是我们手动编写的源文件,必须存在(如果myproc.c缺失,make会直接报错退出)。

(5) 反向执行命令:从源文件到最终目标

当确认所有依赖都已处理后,make会按照依赖链的反向顺序执行命令:

  1. 先执行gcc -E myproc.c -o myproc.i:将源文件myproc.c预处理为myproc.i
  2. 再执行gcc -S myproc.i -o myproc.s:将myproc.i编译为汇编文件myproc.s
  3. 接着执行gcc -c myproc.s -o myproc.o:将myproc.s汇编为目标文件myproc.o
  4. 最后执行gcc myproc.o -o myproc:将myproc.o链接为可执行文件myproc

整个过程就像“链式反应”——只有前一个中间文件生成后,才能执行下一个步骤。

(6) 遇到错误立即停止

在依赖检查或命令执行过程中,若出现以下情况,make会直接退出并报错:

  • 某个依赖文件(如myproc.c)不存在;
  • 命令执行失败(如编译错误,返回非0状态码)。

但需要注意:make只负责检查“依赖是否存在”和“命令是否执行”,不负责检查命令的语法正确性(比如把gcc写成g++,make会执行命令但因错误退出)。

(7) 核心逻辑:只做“必要的事”

make的高效性体现在“增量编译”——它只会重新生成“过时”的文件。例如:

  • 若只修改了myproc.c:make会重新生成myproc.imyproc.smyproc.omyproc
  • 若只修改了myproc.s:make只会重新生成myproc.omyproc,无需处理myproc.imyproc.c
  • 若所有文件都未修改:make会直接提示“myproc已是最新”,不执行任何命令。

二、make/Makefile的扩展语法

  • 刚开始写 Makefile 时,我们可能会像下面这样写
code: code.o
    gcc code.o -o code  # 链接:把 .o 变成可执行文件

code.o: code.s
    gcc -c code.s -o code.o  # 汇编:.s 变 .o

code.s: code.i
    gcc -S code.i -o code.s  # 编译:.i 变 .s

code.i: code.c
    gcc -E code.c -o code.i  # 预处理:.c 变 .i

clean:
    rm -f *.i *.s *.o code  # 清理垃圾文件

这看起来还行,但问题大了:

  • 如果你把 code.c 改名叫 main.c,上面所有提到 code 的地方都得改,漏一个就报错
  • 要是我们加了个新文件 tool.c,又得复制粘贴一堆规则,累得慌

1. 变量的妙用

如果把经常用的文件名、命令起个外号,改的时候只改外号,是不是就方便了

  • 这就是变量的作用

比如下面这样:

BIN=code  # 给可执行文件起个外号叫 BIN
CC=gcc    # 给编译器起个外号叫 CC
SRC=code.c  # 给源文件起个外号叫 SRC
FLAGS=-o   # 给输出参数起个外号叫 FLAGS
RM=rm -f   # 给删除命令起个外号叫 RM

$(BIN):$(SRC)  # 用外号代替具体名字
    $(CC) $(FLAGS) $(BIN) $(SRC)  # 相当于 gcc -o code code.c

clean:
    $(RM) $(BIN)  # 相当于 rm -f code
  • 现在如果要改文件名,比如把 code.c 改成 main.c只需要改 SRC=main.c 就行,其他地方不用动。

2. 自动变量

有时候规则里的文件名会重复。

  • 比如 gcc -o code code.o 里,code 出现了两次。要是文件名很长,写起来超麻烦。这时候“自动变量”就派上用场了,它们能自动代表规则里的目标或依赖文件。

常用的有三个:

  • $@:代表当前规则的“目标文件”(比如上面的 code)。
  • $^:代表当前规则的“所有依赖文件”(比如上面的 code.o)。
  • $<:代表当前规则的“第一个依赖文件”(比如只有一个依赖时,和 $^ 一样)。

举个例子:

$(BIN):$(OBJ)    
    $(CC) -o $@ $^  # 相当于 gcc -o code code.o($@ 是 code,$^ 是 code.o)
    @echo "正在把 $^ 变成 $@"  # 会打印:正在把 code.o 变成 code

%.o:%.c  # 后面会讲这个,先关注自动变量
    $(CC) -c $<  # 相当于 gcc -c code.c($< 是 code.c)
    @echo "正在把 $< 变成 $@"  # 会打印:正在把 code.c 变成 code.o

是不是像用了“占位符”?不用手写具体文件名,Makefile 自动帮你填,少写好多字

3. 批量处理

如果你的项目有多个 .c 文件(比如 a.cb.cc.c),总不能每个都写一条编译规则吧?这时候就需要“模式规则”和“通配符”来批量干活。

(1)模式规则:一键编译所有 .c 文件

模式规则用 % 当通配符,比如 %.o: %.c 表示:“所有 .o 文件都由对应的 .c 文件生成”。

%.o: %.c  # 只要有 x.c,就自动生成 x.o
    $(CC) -c $< -o $@  # 对每个 .c 文件执行:gcc -c x.c -o x.o
%.o: %.c  # 只要有 x.c,就自动生成 x.o

现在不管你有 a.cb.c 还是 c.c,这条规则都能自动处理,不用一个一个写!

(2)通配符函数:自动找文件、改名字

还有两个超实用的工具:

  • wildcard:帮你找出所有符合条件的文件。比如 SRC=$(wildcard *.c),它会自动收集当前文件夹里所有 .c 文件,不管有多少个。
  • patsubst:帮你批量改文件名。比如 OBJ=$(patsubst %.c,%.o,$(SRC)),意思是“把 SRC 里所有 .c 结尾的文件,改成 .o 结尾”。

举个例子:如果当前有 a.cb.c,那么:

SRC=$(wildcard *.c)  # SRC 就等于 "a.c b.c"
OBJ=$(SRC:.c=.o)     # 这是上面那句的简写,OBJ 就等于 "a.o b.o"

这下不用手动列所有文件了,Makefile 会自动帮你找齐

4. 高级小技巧

命令前的 @ 和 -

  • 命令前加 @:只显示命令的结果,不显示命令本身。比如 @echo "编译中...",只会打印“编译中…”,不会显示 echo "编译中...",看起来更清爽。
  • 命令前加 -:就算命令执行失败,也继续往下跑。比如 -rm -f *.o,就算没有 .o 文件,也不会报错中断。

5. 完整示例

最后来看一个能应付大多数小项目的 Makefile,我们一步步拆开看:

BIN=proc  # 可执行文件名叫 proc
CC=gcc    # 用 gcc 编译
SRC=$(wildcard *.c)  # 自动找所有 .c 文件
OBJ=$(SRC:.c=.o)     # 把 .c 换成 .o,比如 a.c → a.o
LFLAGS=-o  # 链接时的输出参数
FLAGS=-c   # 编译时的参数(-c 表示只编译不链接)
RM=rm -f   # 删除命令

# 第一步:链接所有 .o 文件,生成可执行文件 proc
$(BIN):$(OBJ)    
    @$(CC) $(LFLAGS) $@ $^  # 相当于 gcc -o proc a.o b.o(自动找所有 .o)
    @echo "链接完成:把 $^ 变成了 $@"    

# 第二步:编译每个 .c 文件成 .o 文件
%.o:%.c                            
    @$(CC) $(FLAGS) $<  # 相当于 gcc -c a.c(自动处理每个 .c)
    @echo "编译中:$< → $@"

# 声明伪目标
.PHONY: clean test
clean:  # 清理垃圾文件
    $(RM) $(OBJ) $(BIN)  # 删除所有 .o 和 proc
    @echo "清理完毕!"
    
test:  # 测试变量内容
    @echo "源文件列表:$(SRC)" 
    @echo "目标文件列表:$(OBJ)"

这个 Makefile 会干以下事情:

  1. 自动找出当前文件夹所有 .c 文件(比如 a.cb.c)。
  2. 自动算出需要生成的 .o 文件(a.ob.o)。
  3. 逐个把 .c 编译成 .o(不用手动写每个规则)。
  4. 把所有 .o 链接成可执行文件 proc
  5. 提供 make clean 清理垃圾,make test 查看文件列表。

以上就是这篇博客的全部内容,下一篇我们将继续探索Linux的更多精彩内容。

我的个人主页
欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482

非常感谢您的阅读,喜欢的话记得三连哦

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到