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

  • 前言
  • 一、 make/Makefile的推导过程
    • 1. 先看一个完整的Makefile示例
    • 2. make的工作流程
      • (1)寻找Makefile文件
      • (2)确定最终目标
      • (3) 检查最终目标是否需要更新
      • (4) 从最终目标到中间文件
      • (5) 反向执行命令:从源文件到最终目标
      • (6) 遇到错误立即停止
      • (7) 核心逻辑:只做“必要的事”
  • 二、make/Makefile的扩展语法
    • 1. 变量的妙用
    • 2. 自动变量
    • 3. 批量处理
      • (1)模式规则:一键编译所有 .c 文件
      • (2)通配符函数:自动找文件、改名字
    • 4. 高级小技巧
    • 5. 完整示例


前言

  • 前面的博客里我们讲解了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.ogcc myproc.o -o myproc  # 通过链接myproc.o生成可执行文件myproc# 中间目标:生成目标文件myproc.o,依赖于汇编文件myproc.s
myproc.o: myproc.sgcc -c myproc.s -o myproc.o  # 将汇编文件myproc.s编译为目标文件myproc.o# 中间目标:生成汇编文件myproc.s,依赖于预处理文件myproc.i
myproc.s: myproc.igcc -S myproc.i -o myproc.s  # 将预处理文件myproc.i转换为汇编文件myproc.s# 中间目标:生成预处理文件myproc.i,依赖于源文件myproc.c
myproc.i: myproc.cgcc -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.ogcc code.o -o code  # 链接:把 .o 变成可执行文件code.o: code.sgcc -c code.s -o code.o  # 汇编:.s 变 .ocode.s: code.igcc -S code.i -o code.s  # 编译:.i 变 .scode.i: code.cgcc -E code.c -o code.i  # 预处理:.c 变 .iclean: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.cclean:$(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

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/914580.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/914580.shtml
英文地址,请注明出处:http://en.pswp.cn/news/914580.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NFS磁盘共享

步骤&#xff1a;注意事项‌&#xff1a;确保服务端防火墙关闭&#xff0c;或者允许2049端口通信&#xff0c;客户端需具备读写权限。服务器端安装NFS服务器&#xff1a;sudo apt-get install nfs-kernel-server # Debian/Ubuntu sudo yum install nfs-utils # Ce…

ORA-06413: 连接未打开

System.Data.OracleClient.OracleException:ORA-06413: 连接未打开 oracle 报错 ORA-06413: 连接未打开 db.Open();的报错链接未打开&#xff0c;System.Data.OracleClient.OracleException HResult0x80131938 MessageORA-06413: 连接未打开 关于ORA-06413错误&#xff08;…

【PCIe 总线及设备入门学习专栏 5.1.2 -- PCIe EP core_rst_n 与 app_rst_n】

文章目录 app_rst_n 和 core_rst_n 的作用1. core_rst_n — PCIe 控制器内部逻辑复位作用控制方式2. app_rst_n — 应用层/用户逻辑复位作用特点两者关系图示:示例流程(Synopsys EP)rst_sync[3] 的作用详解(复位同步逻辑)为什么使用 rst_sync[3]?图示说明Synopsys 官方手…

Python初学者笔记第二十期 -- (文件IO)

第29节课 文件IO 在编程中&#xff0c;文件 I/O&#xff08;输入/输出&#xff09;允许程序与外部文件进行数据交互。Python 提供了丰富且易用的文件 I/O 操作方法&#xff0c;能让开发者轻松实现文件的读取、写入和修改等操作。 IO交互方向 从硬盘文件 -> 读取数据 -> 内…

Java JUC包概述

Java 的 java.util.concurrent&#xff08;简称 JUC&#xff09;包是 JDK 5 及以后引入的并发编程工具包&#xff0c;旨在解决传统线程模型&#xff08;如 synchronized、wait/notify&#xff09;的局限性&#xff0c;提供更灵活、高效、可扩展的并发编程组件。它极大简化了多线…

LeetCode--44.通配符匹配

前言&#xff1a;不知不觉又断更一天了&#xff0c;其实昨天就把这道题写得差不多了&#xff0c;只是刚好在力扣里面看见了一种新的解法&#xff0c;本来想写出来的&#xff0c;但是我把它推到今天了&#xff0c;因为太晚了&#xff0c;但是今天又睡懒觉了&#xff0c;所以我直…

WHAT - 依赖管理工具 CocoaPods

文章目录1. 什么是 CocoaPods&#xff1f;2. 如何安装 CocoaPods&#xff1f;(1) 确保已安装 Ruby&#xff08;macOS 默认自带&#xff09;(2) 安装 CocoaPods(3) 验证安装3. 在 React Native 项目中使用 CocoaPods(1) 进入 iOS 目录(2) 初始化 Podfile&#xff08;如果不存在&…

C++ Boost Aiso TCP 网络聊天(服务端客户端一体化)

代码功能说明: 程序模式: 主动连接模式:当用户指定对端 IP 和端口时,尝试连接到对端被动监听模式:当用户未指定对端 IP 时,等待其他节点连接线程模型: 主线程:处理用户输入和消息发送接收线程:后台接收并显示对端消息关键组件: std::atomic<bool> connected:原…

WeakAuras 5.12.9 Ekkles lua

3.45猎人宝宝狼 技能恢复宏已知3.45BUG RL技能位会清空&#xff0c;小退大退 BB技能全部激活&#xff0c;修复以前可用宏一键恢复状态-------方法一&#xff1a;宏命令---------------------------------------------------------#showtooltip 狂怒之嚎 /petautocaston [btn:1]…

对于编写PID过程中的问题

当stm32RCT6使用位置环pid控制麦轮转动一定路程时&#xff0c;在这个时间段内想让一边轮胎速度加大应该怎么做&#xff1f;比如我pid的目标脉冲值为9000&#xff0c;在运行到3000的时候车偏左了&#xff0c;那我应该怎样让他回正&#xff0c;我想到的办法是增加其最大的脉冲值&…

LeetCode|Day13|88. 合并两个有序数组|Python刷题笔记

LeetCode&#xff5c;Day13&#xff5c;88. 合并两个有序数组&#xff5c;Python刷题笔记 &#x1f5d3;️ 本文属于【LeetCode 简单题百日计划】系列 &#x1f449; 点击查看系列总目录 >> &#x1f4cc; 题目简介 题号&#xff1a;88. 合并两个有序数组 难度&#xf…

【C++】初识C++(1)

个人主页&#xff1a;我要成为c嘎嘎大王 希望这篇小小文章可以让你有所收获&#xff01; 目录 前言 一、C的第一个程序 二、命名空间 2.1 namespace 的价值 2.2 namespace 的定义 2.2.1 正常的命名空间定义 2.2.2 命名空间可以嵌套 2.2.3 匿名命名空间 2.2.4 同名的name…

在新闻资讯 APP 中添加不同新闻分类页面,通过 ViewPager2 实现滑动切换

在新闻资讯 APP 中添加不同新闻分类页面&#xff0c;通过 ViewPager2 实现滑动切换 核心组件的作用 ViewPager2&#xff1a;是 ViewPager 的升级版&#xff0c;基于RecyclerView实现&#xff0c;支持水平 / 垂直滑动、RTL&#xff08;从右到左&#xff09;布局&#xff0c;且修…

vuex操作state为什么要使用mutations作为规范而不是直接修改state

1. 状态变更的可追踪性 (Trackable Changes)Devtools 集成&#xff1a;Vue Devtools 可以捕获每次 mutation 的执行记录&#xff0c;记录变更前后的 state 快照、参数和调用栈。直接修改 state&#xff1a;Devtools 无法检测到变更来源&#xff0c;导致调试困难&#xff08;如无…

Spring AI 系列之九 - RAG-入门

之前做个几个大模型的应用&#xff0c;都是使用Python语言&#xff0c;后来有一个项目使用了Java&#xff0c;并使用了Spring AI框架。随着Spring AI不断地完善&#xff0c;最近它发布了1.0正式版&#xff0c;意味着它已经能很好的作为企业级生产环境的使用。对于Java开发者来说…

【数据结构】基于顺序表的通讯录实现

目录 1 顺序表的概念及结构 1.1 线性表 1.2 顺序表分类 1.2.1 静态顺序表 1.2.2 动态顺序表 2 顺序表的实现 2.1 顺序表的初始化 2.2 顺序表中数据的增加和修改 2.2.1 顺序表的头插 2.2.2 顺序表的尾插 2.2.3 顺序表的头删 2.2.4 顺序表的尾删 2.2.5 顺序表指定位置…

C语言与汇编混合编程

一、GCC 扩展语法与MSVC约束 &#xff08;一&#xff09;GCC&#xff08;GNU Compiler Collection&#xff09;内联汇编语法 asm("汇编指令");#或者 __asm__("汇编指令");#使用更复杂的语法来指定输入、输出操作数和修改的寄存器&#xff1a; asm volatile…

WPF中的ListBox详解

文章目录简介ListBoxItem选中项目动态列表简介 【ListBox】是列表控件&#xff0c;其内部可包含多个【ListBoxItem】&#xff0c;用户可以从列表中选择一个或多个项&#xff0c;若Item个数超过指定高度&#xff0c;则右侧会自动出现滚动条&#xff0c;非常便捷。尽管逻辑上来说…

【历史人物】【李白】生平事迹

目录 一、李白个人简历 二、个人主要经历 三、个人成就及影响 1、诗 2、词 3、书法 4、剑术 5、理想 四、历史评价 五、趣事 1、李白搁笔 2、赠汪伦 一、李白个人简历 基本信息‌ 姓名&#xff1a;李白&#xff0c;字太白&#xff0c;号青莲居士 性别&#xff1…

HALCON+PCL混合编程

HALCON与PCL的混合编程基础 HALCON和PCL(Point Cloud Library)都是处理3D数据的强大工具&#xff0c;但它们有着不同的设计目标和数据结构。HALCON专注于机器视觉应用&#xff0c;提供了丰富的图像处理和分析功能&#xff1b;而PCL则是专门为点云处理设计的开源库。 要实现两者…