作者:金婉山_461 | 来源:互联网 | 2023-08-31 11:19
值得思考的问题目标文件(.o)是否只依赖于源文件(.c)?编译器如何编译源文件和头文件?编译行为带来的缺陷预处理器将头文件中的代码直接插入源文件编译器只通过预处理后的源文件产生目标
值得思考的问题
目标文件 (.o) 是否只依赖于源文件 (.c)?
编译器如何编译源文件和头文件?
编译行为带来的缺陷
预处理器将头文件中的代码直接插入源文件
编译器只通过预处理后的源文件产生目标文件
因此,
下面的 makefile 有没有问题?
问题的提出
makefile
TARGET := hello.out
CC := gcc
SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
$(TARGET) : $(OBJS)
$(CC) -o $@ $^
$(OBJS) : %.o : %.c
$(CC) -o $@ -c $<
当前的目录文件
当我们修改了 foo.h 中的内容后,结果如下
并没有再次进行编译,这导致最终可执行文件没有重新生成。
实验中的解决方案
头文件作为依赖条出现于每个目标对应的规则中
当头文件改动,任何源文件都将被重新编译 (编译低效)
当项目中头文件数量巨大时,makefile 将很难维护
疯狂的想法
通过命令自动生成对头文件的依赖
将生成的依赖自动包含进 makefile 中
当头文件改动后,自动确认需要重新编译的文件
预备工作 (原材料)
Linux 命令 sed
编译器依赖生成选项 gcc -MM (gcc -M)
Linux 中的 sed 命令
sed 是一个流编辑器,用于流文件的修改 (增/删/查/改)
sed 可用于流文本中的字符替换
sed 的字符串替换方式为 sed 's:src:des:g'
sed 的正则表达式支持
在 sed 中可以用正则表达式匹配替换目标
并且可以使用匹配的目标生成替换结果
gcc 关键编译选项
生成依赖关系
gcc -M test.c
gcc -MM test.c
小技巧:拆分目标的依赖
将目标的完整依赖拆分为多个多个部分依赖
预备工作
makefile
.PHONY : test a b c
test : a b
test : b c
test :
@echo "$^"
运行结果如下所示:
我们将 test 目标的依赖拆分成两部分,和直接将 test 目标的依赖写成一起是一样的。
makefile 中的 include 关键字
类似 C 语言中的 include
将其它文件的内容原封不动的搬入当前文件
make 对 include 关键字的处理
在当前目录搜索或指定目录搜索目标文件
- 搜索成功:将文件搬入当前 makefile 中
- 搜索失败:产生警告
- 以文件名作为目标查找并执行对应规则
- 当文件名对应的规则不存在时,最终产生了错误
初探 include 关键字
makefile
.PHONY : all
include test.txt
all :
@echo "this is all"
test.txt :
@echo "this is test.txt"
@touch test.txt
test.txt
other :
@echo "this is $@"
执行结果如下所示:
首先 make 会查找当前目录中是否存在 test.txt 文件,当前目录中存在该文件,所以 make 会将 test.txt 中的内容插入到 include 的地方,other 成为了顶层目标,所以我们在执行 make 的时候,其实是在执行 other 所对应的命令。
我们将当前目录下的 test.txt 文件删除,再次执行 make,运行结果如下所示:
此时 make 查找不到对应的文件,所以首先会产生一个警告,然后去查找当前 makefile 中是否存在对应的目标,当前 makefile 中存在 test.txt 目标,所以会执行目标所对应的命令,这时顶层的目标为 all,所以又会去执行 all 所对应的命令。
makefile 中命令的执行机制
规则中的每个命令默认是在一个新的进程中执行 (Shell)
可以通过接续符 (;) 将多个命令组合成一个命令
组合的命令依次在同一个进程中被执行
set -e 指定发生错误后立即退出执行
下面的代码想要实现功能?有没有问题?
每个命令都是 Shell 开启一个新的进程执行的,进程之间不会相互影响,所以 subtest 是创建在当前目录下的,而不是 test 目录下的。
makefile 的命令执行
makefile
.PHONY : all
all :
set -e; \
mkdir test; \
cd test; \
mkdir subtest
set -e 指定发生错误后立即退出执行,并通过接续符将四个命令组合成一个命令,Shell 此时只会创建一个进程去执行这些命令,所以 subtest 是创建在 test 目录下的。
解决方案的初步思路
通过 gcc -MM 和 sed 得到 .dep 依赖文件 (目标的部分依赖)
通过 include 指令包含所有的 .dep 依赖文件
- 技术点:当 .dep 依赖文件不存在时,使用规则自动生成
解决方案原型
makefile
.PHONY : all clean
CC := gcc
MKDIR := mkdir
RM := rm -rf
SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)
-include $(DEPS)
all :
@echo "this is $@"
%.dep : %.c
@echo "creating $@ ..."
@set -e; \
$(CC) -MM -E $^ | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@
clean :
$(RM) $(DEPS)
.dep 依赖文件记录了一个目标文件所对应的依赖关系;如果对应的依赖文件不存在,则通过 include 去执行目标所对应的命令,gcc -MM 生成目标文件的依赖关系,通过 sed 将这些依赖关系都添加 objs/ 前缀,并重定向到对应的依赖文件中。