Makefile精读 · M03-02 | 展开时机:递归展开 vs 简单展开,以及覆写规则

发布时间:2026/9/9 19:20:02

Makefile精读 · M03-02 | 展开时机:递归展开 vs 简单展开,以及覆写规则 模块M03 变量体系篇号M03-02 / 41预计阅读4045 分钟主线GNU Make加厚篇★文章目录本篇目标30 秒速览正文1. 两个时刻定义时 vs 使用时2. 为何「递归」会咬自己自引用陷阱3. : 里引用递归变量定义那一刻拍快照4. 的展开取决于左边原来是谁4.1 左边是简单变量4.2 左边是递归变量4.3 实战原则比背条文有用5. 配方中的展开时机6. 函数与 $(shell)展开几次就跑几次7. 覆写规则谁盖过谁7.1 命令行覆盖7.2 override 反覆盖7.3 ? 与「已有值」7.4 同文件多次赋值8. 调试展开$(info) / $(warning) / make -p9. 场景题加厚场景 1include 顺序场景 2以为改了依赖列表场景 3命令行以为没盖住10. 对照表与推荐策略读 Makefile 检查清单练习判断题对 / 错改错题动手题下一篇预告本篇目标打通 Make 变量最难的认知坑什么时候展开。分清递归变量与简单变量:、追加时的展开差异以及命令行 / 环境 /override/?谁覆盖谁。学完本篇你再遇到「我改了变量怎么没生效」「Recursive variable references itself」「$(shell)跑了好几次」一类问题能按展开时刻定位。30 秒速览简单变量:定义时把右侧展开并存成最终字符串之后引用只是取出。递归变量定义时几乎原样存下右侧文本每次引用再展开。配方中的变量默认在执行该配方前再展开与规则解析阶段的展开不同。若左边原是:简单变量追加段按简单规则处理若原是追加段偏递归语义——别混用到晕。自引用VAR $(VAR) -O2是经典雷用:或先写全再赋。覆写链实用记忆override 命令行 Makefile 普通赋值?只在「尚无值」时写入。正文1. 两个时刻定义时 vs 使用时把变量想成两种盒子类型赋值盒子里装什么简单simple / immediately expanded:/::已经展开好的字符串递归recursively expanded一段待展开的文本模板实验 A——简单A : 1 B : $(A) A : 2 .PHONY: show show: echo B$(B)make show→B1因为定义B时$(A)已收成1。实验 B——递归A 1 B $(A) A 2 .PHONY: show show: echo B$(B)make show→B2因为用到$(B)时才去展开$(A)此时A已是2。读代码第一问这个变量是还是:不问这个就不要断言它的值。2. 为何「递归」会咬自己自引用陷阱CFLAGS -Wall CFLAGS $(CFLAGS) -O2展开$(CFLAGS)时要展开右侧的$(CFLAGS)……无限递归。GNU Make 报错类似*** Recursive variable CFLAGS references itself (eventually). Stop.错误写法的意图其实是「追加」正确写法CFLAGS : -Wall CFLAGS -O2或CFLAGS : -Wall -O2加厚记忆看到VAR $(VAR) …且VAR是定义的递归变量 → 先当炸弹。3.:里引用递归变量定义那一刻拍快照X foo Y : $(X) X bar # Y 已是 fooX foo Y $(X) X bar # 用到 Y 时是 bar工程含义用:可以把「当时的配置」冻住用则跟着后续赋值漂。大型 Makefile 里若前半定义FLAGS …后半又改FLAGS再看前半用引用它的变量——值可能已变。4.的展开取决于左边原来是谁4.1 左边是简单变量A : base A $(OTHER)追加时$(OTHER)通常按简单赋值的规则在追加这一刻展开结果并回简单变量。适合确定要把当前OTHER的内容拼进去。4.2 左边是递归变量A base A $(OTHER)追加段往往以递归方式保留/处理使得之后OTHER变化仍可能影响A具体细节随历史代码与版本让人晕——实战原则见下。4.3 实战原则比背条文有用配置型变量统一用:起步再用追加。尽量避免「同一变量先再疯狂」。读到混用时用小实验或$(info …)打印见第 8 节不要凭感觉。推荐骨架CFLAGS : -Wall CFLAGS -O2 CFLAGS $(EXTRA_CFLAGS)若EXTRA_CFLAGS希望延迟到最后才定CFLAGS -Wall -O2 $(EXTRA_CFLAGS) EXTRA_CFLAGS -g这是刻意使用递归的高级写法团队不熟时宁可用:并保证赋值顺序正确。5. 配方中的展开时机MSG hello .PHONY: t t: echo $(MSG)规则解析阶段会记下配方文本执行t的配方前再展开其中的$(MSG)。因此MSG first t: echo $(MSG) MSG secondmake t通常打印second——只要最终执行前MSG已是second。对照若写成MSG : first RECIPE : echo $(MSG) MSG : second t: $(RECIPE)则RECIPE在定义时已变成echo first与递归引用行为不同。启示配方里直接写$(VAR)递归或简单都是「执行前取当前值」的常见路径与「先把命令存进另一个:变量」不是一回事。6. 函数与$(shell)展开几次就跑几次NOW $(shell date %s) .PHONY: a b a: echo $(NOW) b: echo $(NOW)NOW是递归变量时每次展开$(NOW)都可能再跑一遍date。make a跑一次若某次构建多次展开NOWshell 就多次。改成NOW : $(shell date %s)配置阶段跑一次之后复用同一字符串。加厚原则需求写法探测一次即可: $(shell …)或!故意每次不同才用递归 $(shell)少见这也解释了「为什么我的 Makefile 一启动就很慢」——全文搜$(shell与!。7. 覆写规则谁盖过谁实用优先级排错版不追求标准条文级细节override 赋值 ≥ 命令行赋值 ≥ Makefile 普通赋值 ? 仅在「尚无值」时写入 环境变量常可作为「已有值」影响 ?make -e 会抬高环境优先级慎用7.1 命令行覆盖CFLAGS : -Wall -O2makeCFLAGS-O0本次构建中配方里的$(CFLAGS)通常是-O0。7.2override反覆盖override CFLAGS : -Wall -O2即使make CFLAGS-O0仍用文件中的-Wall -O2。读到override作者认为该值不允许被随手改掉。7.3?与「已有值」CC ? gcc情况结果makeCC为gccmake CCclangclang环境已有CCclang再make多为clang?不覆盖已有7.4 同文件多次赋值后写的普通赋值覆盖先前在同类来源下CFLAGS : -Wall CFLAGS : -Wall -O2最终-Wall -O2。则是叠加而非整段替换。8. 调试展开$(info)/$(warning)/make -p在可疑位置插入$(info CFLAGS at parse [$(CFLAGS)])或show: echo CFLAGS$(CFLAGS)区别手段何时打印$(info …)解析 Makefile 过程中展开发生时配方里echo执行目标时make -p倾倒数据库含变量 origin很大M09查变量来源可用配方show: echo origin$(origin CFLAGS) value$(CFLAGS)origin可能是file/command line/environment/override等——判断「为什么盖不掉」极有用。9. 场景题加厚场景 1include 顺序CFLAGS : -Wall include extra.mkextra.mk含CFLAGS -DDEBUG。最终含-DDEBUG。若include在前、CFLAGS : -Wall在后整段覆盖则-DDEBUG可能被抹掉——顺序即语义。场景 2以为改了依赖列表SRC main.c OBJS $(SRC:.c.o) SRC main.c util.cOBJS若是递归变量用到时随SRC变若写成SRC : main.c OBJS : $(SRC:.c.o) SRC : main.c util.c则OBJS仍只有main.o。这是:快照的典型「没生效」错觉。场景 3命令行以为没盖住文件有override CFLAGS : -O2你make CFLAGS-O0无效——不是 make 坏了是override。10. 对照表与推荐策略写法定义时使用时推荐:展开并存储取出默认存模板再展开刻意延迟时接:追加并处理好展开—叠选项?条件写入—默认可覆盖项override提高优先级—锁定关键选项推荐策略一句话:起步叠加?给默认少用裸弄不清就$(info)/origin。读 Makefile 检查清单关键变量是还是:有没有自引用$(VAR)$(shell)/!挂在递归变量上吗可能多次执行与include顺序会不会被后面的:整段覆盖命令行覆盖失败时搜override与origin「列表没更新」是不是:快照拍早了练习判断题对 / 错B $(A)在定义时一定把A的当前值写进B。CFLAGS $(CFLAGS) -O2对递归变量CFLAGS是安全的追加写法。NOW : $(shell date)通常比NOW $(shell date)更少重复执行 date。override可让 Makefile 中的赋值压过命令行传入的同名变量。参考答案错。那是:的行为存的是模板。错。会导致自引用递归错误。对。对。改错题SRC : a.c OBJS : $(SRC:.c.o) SRC b.c app: $(OBJS) $(CC) -o $ $(OBJS)作者期望a.o b.o实际常常只有a.o。为什么怎么改参考答案原因OBJS : …在只含a.c时已快照随后SRC b.c不再更新OBJS。改法一先写全SRC再生成OBJSSRC : a.c b.c OBJS : $(SRC:.c.o)改法二保持递归需谨慎SRC : a.c SRC b.c OBJS $(SRC:.c.o)动手题完成本篇实验 A/B确认B的差异。故意写自引用CFLAGS $(CFLAGS) -g记录报错原文。对某变量打印$(origin VAR)分别在「文件赋值 / 命令行赋值 / override」下对比。下一篇预告M03-03《自动变量$$$^$?$*与目录变体》— 规则配方里那些「看不见赋值却到处出现」的符号。学完 M03-03加厚篇模式规则的配方才能真正写熟、读熟。
延伸阅读

更多相关文章

2026/9/8 23:29:06

AI模型调用成本优化:OpenRouter智能路由策略与工程实践

最近在折腾几个 AI 项目时,我发现一个挺有意思的现象:不少团队在模型调用成本上卡住了。不是技术实现不了,而是每次调用都在烧钱,尤其是面对高频、长文本或复杂推理任务时,账单增长速度比代码跑得还快。这时候&#xf…

2026/9/6 14:27:11

Windows Server 2025界面回归Win10风格:企业部署与混合云集成指南

Windows Server 2025 作为微软最新的服务器操作系统,带来了一个有趣的变化:它的桌面体验界面开始向 Windows 10 风格回归,而不是延续 Windows 11 的设计语言。这种"反向操作"背后反映了企业用户对稳定性和操作习惯的强烈需求。从技…

2026/9/3 14:26:57

AI时代数据处理与统计分析实战指南

1. AI时代的数据处理与统计分析实战指南在数据爆炸的今天,AI技术正在彻底改变传统数据处理与统计分析的工作方式。作为一名长期奋战在数据科学一线的从业者,我见证了从Excel手工操作到Python自动化分析的完整演进历程。本文将分享如何利用现代AI工具和技…

2026/9/9 19:15:13

制造业智能体平台落地指南:从车间调度到质量追溯

上个月跟一个做精密机加工的老板聊天,他一句话点醒我:工厂里最值钱的不是某台进口设备,是老师傅脑子里那套排产经验。老师傅一走,经验就没了,产线效率跟着掉一截。另一边,质量部门天天加班追批次追溯记录&a…

2026/9/9 19:15:13

ARM64交叉编译OpenSSL静态库完整指南:从Configure到链接排错

简介:面向 iOS 开发者的 OpenSSL arm64 静态库资源包,专门解决苹果全面转向 ARM 芯片后,应用在 iPhone 和 iPad 上可能遇到的架构不兼容问题。资源包含已交叉编译的 libssl.a 与 libcrypto.a,可直接接入 Xcode 工程,用…

2026/9/9 19:15:13

基于MATLAB/Simulink的风光储氢系统仿真建模与能量管理策略

我在新能源系统仿真的项目里已经摸爬滚打了几年,MATLAB/Simulink 下的“风光储电解制氢与氢燃料电池系统仿真模型”是我投入精力最多、也是沉淀出最多经验的一个方向。这个模型把光伏发电、风力发电、储能电池、电解槽制氢和氢燃料电池发电整合在同一个仿真环境里&a…

2026/9/9 19:15:13

Agilent 34970A LabVIEW驱动全解析:从VISA到SCPI

简介:一套面向安捷伦34970A数据采集器的LabVIEW驱动程序例程,适用于在LabVIEW环境下开发测试测量与自动化控制应用的工程师,解决设备通信、测量参数配置、多通道扫描切换和数据读取等常见需求。压缩包共72个文件,以60个vi子程序为…

2026/9/9 19:15:13

STM32驱动HX711压力传感器称重实战:从接线到标定

简介:一款以STM32为主控、结合HX711高精度24位A/D芯片的压力传感器电子秤项目资源(说明文档中亦含STC90C52RC方案描述),面向单片机初学者、电子设计竞赛选手以及需要快速落地称重功能的开发者。该系统在0~5kg量程内误差…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/9 0:00:48

MHS模型硬件标准:让大模型像调用软件一样控制物理设备

让Claude真正看着显微镜说“这个细胞形态不太对”,或者让大模型自己调一版机械臂的运动轨迹,这事儿听上去已经很接近科幻片了。但你真上手试一次就会发现,模型不缺智商,缺的是一个能插进显微镜、机械臂、激光控制器里的“通用插座…

2026/9/9 0:00:48

AI五大核心方向详解:从机器学习到大模型,零基础转行选哪条?

会有人告诉我,他想转行学AI,但打开招聘网站一看直接傻眼:机器学习、深度学习、自然语言处理、计算机视觉、大模型应用……满屏都是这些词,好像每个都会一点,又好像每个都离自己很远。还有人上来就问“学Python还是学Ja…

2026/9/9 0:00:49

从50行最小循环到生产级AI引擎:工程化改造全解析

直接说干货。这一章我写的不是那种"hello world跑通某个模型"的教程,而是把AI引擎当做一个真正要上线、要被人调用、要扛流量的系统来聊。从最初只有50行的最小循环,到能够承载生产流量的AI引擎,中间差的不是代码量,而是…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码