Makefile需显式定义C++编译规则:用.CPP.o或更可靠的显式规则(如main.o: main.cpp),设置CXXFLAGS含-std=c++17等,自动生成依赖文件%.d并-include,链接时用g++且显式列出所有.o目标。

Makefile 里怎么写 C++ 编译规则
默认 make 不认识 .cpp 文件,也不会自动调用 g++。必须显式声明编译器、后缀规则和依赖关系。
最简可用的规则是:
.CPP.o:
g++ -c $(CXXFLAGS) $< -o $@其中 $ 是第一个依赖(如 main.cpp),$@ 是目标(如 main.o)。注意:.CPP.o 是 GNU make 的隐含规则写法,大小写敏感,.cpp.o 在旧版 make 中可能不生效。
- 推荐用更明确的显式规则,避免隐含规则干扰:
main.o: main.cpp+ 缩进命令 -
CXXFLAGS应包含-std=c++17 -Wall -Wextra等,别漏掉-I头文件路径 - 不要写
.cpp.o:后直接跟空格或 tab 混用——tab 字符必须严格,且只能是第一个字符
怎么组织多文件项目的依赖和链接
目标文件(.o)之间没有隐式依赖,main.o 用了 utils.h,但 make 不知道它该重编译——除非你告诉它。
立即学习“C++免费学习笔记(深入)”;
手动写依赖容易出错,推荐用编译器自动生成:
%.d: %.cpp
g++ -MM $(CXXFLAGS) $< | sed 's,\(.*\)\.o[ :]*,\1.o \1.d : ,g' > $@再在 Makefile 开头用 -include 加载所有 .d 文件:
-include $(SRCS:.cpp=.d)
-
SRCS := main.cpp utils.cpp这种变量定义要放在-include前面,否则$(SRCS:.cpp=.d)展开为空 - 链接阶段用
g++而不是gcc,否则 C++ 标准库(如std::string)可能链接失败 - 可执行文件目标要显式写依赖所有
.o:myapp: main.o utils.o,不能只靠后缀规则
为什么改了头文件,make 却不重新编译
这是新手最常遇到的问题:修改了 utils.h,运行 make 却说“up to date”。根本原因是 Makefile 里没声明 .o 文件依赖对应头文件。
上面用 -MM 生成的 .d 文件内容类似:
main.o main.d: main.cpp utils.h
这样当 utils.h 时间戳更新,main.o 就会被判定为过期。
- 如果不用自动生成,手写依赖必须完整列出所有
#include的头文件,包括系统头(如)通常不用列,但项目内头文件一个都不能少 -
sed命令里的正则用于把main.o: main.cpp utils.h改成main.o main.d: main.cpp utils.h,确保.d文件自身也被追踪 - 第一次运行
make时.d文件不存在,-include会静默忽略,所以首次构建不会失败
要不要用 CMake 或 Meson 替代 Makefile
纯手工写 Makefile 在中大型项目里很快失控:交叉编译、测试目标、安装规则、第三方库查找(pkg-config)、宏开关(-DDEBUG)都会让 Makefile 变得难读难维护。
如果你只是练手、写工具脚本、或嵌入式受限环境,一个 20 行以内的 Makefile 完全够用;但只要项目超过 3 个源文件、有子目录、需要跨平台,就该考虑 CMake。
- CMakeLists.txt 里
add_executable(myapp main.cpp utils.cpp)会自动处理依赖和链接,比手写 Makefile 稳定得多 - Makefile 里写错一个 tab 或漏一个反斜杠,错误信息往往是
*** missing separator,而 CMake 报错通常指向具体行 - 别为了“学 Makefile”硬扛——现代 C++ 项目几乎都用 CMake,Makefile 更多是理解构建原理的入口,不是日常主力
真正容易被忽略的是:哪怕只用 Makefile,也该把 CXX、CXXFLAGS、SRCS、TARGET 提取成变量,而不是在命令里硬编码路径和参数。否则改一个编译选项就得全局搜索替换。










