阅读《GNU Make项目管理》时,编写Fortran项目Makefile的自动依赖困惑
解析自动依赖生成的Makefile代码
这段Makefile代码是自动生成C项目依赖关系的经典实现,核心是让make能自动追踪头文件(.h)的变化,不用手动维护每个目标的依赖。我来逐部分拆解,帮你理清逻辑:
1. make-depend宏的核心作用
这个宏是生成依赖文件(.d)的关键,逐行拆解如下:
define make-depend $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M $1 | \ $(SED) 's,\($$(notdir $2)\) *:,\$$(dir $2) $3: ,' > $3.tmp $(MV) $3.tmp $3 endef
- 第一行:生成原始依赖
调用C编译器($(CC)),传入编译选项,用-M指令生成依赖关系。$1是宏的第一个参数(也就是当前处理的源文件,比如foo.c)。编译器会输出类似foo.o: foo.c bar.h baz.h的内容,意思是foo.o依赖源文件和相关头文件。 - 第二行:替换依赖目标
这是最关键的一步:用sed把输出里的目标文件名(比如foo.o)替换成对应的依赖文件名(比如foo.d),同时保留目标文件的路径。举个例子:如果目标文件在obj/foo.o,$(notdir $2)提取出foo.o,$(dir $2)拿到obj/,$3是obj/foo.d,替换后原本的foo.o: ...就变成obj/ obj/foo.d: foo.c bar.h——这是为了兼容目标文件在子目录的场景。 - 第三行:原子更新依赖文件
先把生成内容写到临时文件,再用mv替换正式的.d文件。这么做是为了避免生成过程中出错时,覆盖掉原来有效的依赖文件,保证操作的原子性。
2. %.o: %.c规则的执行流程
这个模式规则处理所有.c文件生成.o文件的全流程:
%.o: %.c $(call make-depend,$<,$@,$(subst .o,.d,$@)) $(COMPILE.c) -o $@ $<
- 第一步:生成依赖文件
$(call make-depend,$<,$@,$(subst .o,.d,$@))里,$<是当前源文件(比如foo.c),$@是目标文件(比如foo.o),第三个参数把.o后缀换成.d(比如foo.d)。这一步会生成对应源文件的依赖描述文件。 - 第二步:编译生成目标文件
$(COMPILE.c)是Make的内置变量,展开后就是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c,负责把.c文件编译成.o目标文件。
3. 「目标文件和依赖文件已逻辑关联」的含义
这是这段代码的核心优势:正常情况下,-M生成的依赖只告诉make「.o文件依赖.c和.h文件」,但这段代码多做了一层关联:
- 它让**.d文件本身也成为一个依赖目标**,依赖对应的
.c和.h文件(从sed替换后的内容可以看出); - 当你在Makefile里用
include $(wildcard *.d)引入这些.d文件后,make会同时识别两层关系:.o文件依赖.c和所有关联头文件(从.d文件的内容获取);.d文件依赖.c和所有关联头文件。
举个实际场景就能明白:
- 第一次运行
make,生成foo.d和foo.o; - 修改
bar.h后,make发现foo.d依赖bar.h,会先重新调用make-depend生成最新的foo.d; - 接着
make从新的foo.d里得知foo.o依赖bar.h,于是自动重新编译foo.o。
这样一来,不仅目标文件会跟着头文件变化自动重建,依赖文件本身也会保持最新,不会出现「依赖关系过时」的问题——这就是两者的逻辑关联:.d文件为.o文件提供依赖信息,同时.d文件自身的更新也由依赖的源文件/头文件触发。
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

