Makefile 为什么要用 src 推导 obj,而不直接列出 .o 文件?
结论
可以只定义 obj,不必额外定义 src。如果构建规则最终依赖的目标文件相同,两种写法可以实现相同的构建行为。
定义 src 并由它推导 obj,可以列出源文件集合;需要同时使用两份列表时,也能减少重复维护。src 和 obj 都是普通变量名,make 不会因为它们叫这些名字就赋予特殊含义。
原示例先要修正变量大小写
问题中的代码是:
src = file1.c file2.c
obj = $(SRC:.c=.o)
make 的变量名区分大小写,src 和 SRC 是两个不同的变量。如果没有在其他地方定义 SRC,这里的 obj 通常会展开为空。
按原来的意图,应写成:
src = file1.c file2.c
obj = $(src:.c=.o)
$(src:.c=.o) 是变量替换引用,会把 src 中每个以 .c 结尾的单词的后缀替换为 .o。这个例子的结果是:
file1.o file2.o
这一步只生成文件名列表,不会编译源文件,也不会检查文件是否存在。
只列出 obj,make 怎么找到源文件?
编译关系由规则决定。以 GNU make 为例,下面的 Makefile 直接列出目标文件,并显式提供编译规则:
obj = file1.o file2.o
app: $(obj)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
命令行开头需要使用实际的 Tab 字符。
这里的 %.o: %.c 表示 .o 文件依赖同名的 .c 文件。例如,构建 file1.o 时,对应的源文件是 file1.c,不需要再通过 src 告诉 make 源文件叫什么。
规则中的自动变量含义是:
$@:当前目标文件。$<:第一个依赖文件,在这条编译规则中就是对应的.c文件。$^:所有依赖文件,重复项只保留一次;在这里用于链接目标文件。
GNU make 默认还提供一些内建隐式规则,包括常见的 C 编译规则。不过,内建规则可能被禁用或覆盖,上面的示例通过显式规则说明了完整的构建关系。
为什么仍然经常定义 src?
当源文件列表有其他用途时,单独定义它会更方便。例如,同一组 .c 文件可能还要交给静态分析工具、代码格式化工具,或作为源码打包的输入。
这时可以只维护一份清单:
src = file1.c file2.c
obj = $(src:.c=.o)
新增 file3.c 时,只需把它加入 src,对应的目标文件名就会出现在 obj 中。如果分别手写两份列表,就需要保证它们始终一致。
如果整个 Makefile 只需要目标文件列表,可以省去用不到的 src,直接写:
obj = file1.o file2.o
选择时需要注意什么?
根据实际需要维护文件列表:需要直接使用源文件清单时,定义 src 并推导 obj;只需要目标文件清单时,可以直接定义 obj。
还要区分文件名替换和依赖关系:$(src:.c=.o) 只处理文件名,不会自动发现目录里的新文件,也不会记录 .c 文件包含了哪些头文件。头文件变化后是否重新编译,需要由另外的依赖规则或依赖生成机制处理。
备注:内容仅供参考。
