clang -mmd 生成 .d 文件需配合 -o 指定目标对象文件,否则不写入;-mp 防止头文件删除导致 make 失败;-mf 和 -mt 控制路径与目标名;最后必须用 -include 将 .d 文件加载进 makefile。

Clang -MMD 会生成 .d 文件,但默认不写入任何内容
直接用 clang++ -MMD main.cpp -c 不会报错,但也不会生成可用的 main.d —— 因为 -MMD 本身不指定输出目标名,Clang 会尝试把依赖写进 main.o 对应的同名 .d 文件,但若没同时用 -o main.o,它就找不到“对应目标”,干脆跳过写入。这是最常被忽略的前提。
正确做法是:必须显式指定输出目标对象文件,-MMD 才能推导出 .d 文件名:
-
clang++ -MMD -c main.cpp -o main.o→ 生成main.d -
clang++ -MMD -c utils.cpp -o obj/utils.o→ 生成obj/utils.d
生成的 main.d 内容形如:main.o: main.cpp utils.h config.h,不含系统头(#include <...></...>),这点和 -MD 不同。
为什么加 -MP 是刚需,而不是可选
不加 -MP 时,如果某天你删掉了 utils.h,下一次 make 就会报错:*** No rule to make target 'utils.h', needed by 'main.o'. Stop.。因为 Make 解析 main.d 时发现依赖项 utils.h 不存在,又没定义怎么生成它,就直接失败。
-MP 的作用是:在 .d 文件末尾额外添加一行空目标,比如:
main.o: main.cpp utils.h config.h utils.h: config.h:
这样即使头文件被删,Make 也能顺利跳过该依赖继续执行。实际项目里头文件增删频繁,漏掉 -MP 意味着每次重构头文件都得手动清理依赖缓存。
用 -MF 和 -MT 避免路径混乱和目标名错位
当构建目录和源码目录分离(比如源码在 src/,目标在 build/)时,-MMD 自动推导的 .d 路径会基于 -o 输出路径,但依赖项里的源文件路径仍是相对原路径的,容易导致 Make 找不到 src/main.cpp。
Clang 22.1.3 Windows 64 位历史版本安装包,适合旧项目兼容、LLVM/Clang 工具链回退、编译行为对比、链接问题复现和 C/C++ 构建环境维护。
更可控的方式是绕过自动推导,用 -MF + -MT 显式控制:
-
-MF build/main.d:强制依赖写入指定位置 -
-MT "build/main.o":声明这条依赖关系的目标是build/main.o,不是main.o - 配合
-MP,完整命令:clang++ -MMD -MP -MF build/main.d -MT "build/main.o" -c src/main.cpp -o build/main.o
这样生成的 build/main.d 第一行就是:build/main.o: src/main.cpp src/utils.h,和你的 Makefile 规则完全对齐。
生成后必须 include 到 Makefile,否则等于没做
.d 文件只是文本,Make 默认完全无视它。你得在 Makefile 里主动 include 这些文件,而且要放在所有规则之后、首次调用 make 前就加载:
all: main.out <p>main.out: main.o utils.o $(CXX) $(CXXFLAGS) -o $@ $^</p><p>main.o: src/main.cpp $(CXX) $(CXXFLAGS) -MMD -MP -MF build/main.d -MT "build/main.o" -c $</p><p>utils.o: src/utils.cpp $(CXX) $(CXXFLAGS) -MMD -MP -MF build/utils.d -MT "build/utils.o" -c $</p><p>-include build/*.d</p>
注意三点:
-
-include开头的短横是必须的,表示“即使文件不存在也不报错” -
build/*.d必须能匹配到实际生成的文件,路径别写错 - 不能把
-include放在main.o规则之前,否则第一次运行时.d还没生成,Make 会忽略全部依赖
依赖生成只是半截路,真正让头文件改动触发重编译,靠的是这行 -include 和它背后 Make 的增量解析逻辑。漏掉任一环节,-MMD 就只是在硬盘上悄悄多建了几个空文件。










