11-14 - 自动生成依赖关系

1. 编译行为带来的缺陷

1.1 问题

  • 目标文件(.o)是否只依赖于源文件(.c)?
  • 编译器如何编译源文件和头文件?

在这里插入图片描述

1.2 缺陷

  • 预处理器将头文件中的代码直接插入源文件
  • 编译器只通过预处理后的源文件产生目标文件
  • 因此,规则中以源文件为依赖,命令可能无法执行

1.3 实验

在这里插入图片描述

#include <stdio.h>
#include "func.h"

void foo()
{
	printf("foo(): %s\n", HELLO);
}

#ifndef FUNC_H
#define FUNC_H

#define HELLO "Hello Makefile"

void foo();

#endif

#include "func.h"

int main()
{
	foo();
	
	return 0;
}

OBJS := func.o main.o

hello.out: $(OBJS)
	@gcc -o $@ $^
	@echo "Target File ==> $@"

$(OBJS):%.o:%.c
	@gcc -o $@ -c $^

1.4 结果

1.4.1 新的问题

执行 make,可以得到正确的可执行文件,修改 func.h 中的宏定义,将 “HELLO” 宏改为 “Hello Makefile”,此时,再次执行 make,提示文件是最新的。并不是我们想要的结果。

在这里插入图片描述

1.4.2 初步解决方案

  • make 解释器根据文件新旧关系判断出并不需要重新编译,但是,这并不是我们想要的结果
  • 将 func.h 加入到依赖关系中去,这样头文件的改变,make 就能感知到。
OBJS := func.o main.o

hello.out: $(OBJS)
	@gcc -o $@ $^
	@echo "Target File ==> $@"

$(OBJS):%.o:%.c func.h
	@gcc -o $@ -c $<

在这里插入图片描述

1.4.3 初步方案问题

  1. 头文件作为依赖条件出现在每个目标对应的规则中(即使这个目标文件与该头文件没有任何关系)
  2. 当头文件改动时,任何源文件都将被重新编译(编译低效)
  3. 当项目中头文件数量巨大时,Makefile 很难维护

1.5 目标

解决方案:

  1. 通过命令自动生成对头文件的依赖
  2. 讲生成的依赖自动包含进 Makefile 中
  3. 当头文件改动后,自动确认需要重新编译的文件

2. 预备工作

2.1 sed 命令

  • sed 是一个流编辑器,用于流文本的修改(增删改查)
  • sed 可用于流文本中的字符串替换
  • sed 字符串替换方式为:sed 's:src:dst:g',其中,s 和 g 是固定格式,src 为待替换的字符串,dst 为替换后的字符串。
    在这里插入图片描述
    在这里插入图片描述
  • sed 还支持正则表达式,可以使用匹配的目标生成替换结果

在这里插入图片描述
在这里插入图片描述

  • \(.*\)\.o[ :]*匹配以.o结尾的文件名,包括它的路径结构,后边的[ :]表示文件名后有任意空格或者冒号(:)也可以进行匹配。替换部分则表示在匹配到的文件名前加上前缀objs/

2.2 gcc 关键编译选项

  • 生成依赖关系-M-MM
    -M:获取目标的完整依赖关系
    -MM:获取目标的部分依赖关系

在这里插入图片描述

  • 将 gcc 的这个特殊选项和 sed 命令结合在一起

在这里插入图片描述

  • 拆分目标的依赖:将目标的完整依赖拆分为多个部分依赖

在这里插入图片描述
在这里插入图片描述

2.3 include 关键字

  • 类似于 C 语言中的 include 关键字
  • 将其它文件的内容原封不动的搬到当前文件
  • make 对 include 关键字的处理,在当前目录搜索或指定目录搜索目标文件:
    • 搜索目标文件成功:将文件内容搬入当前 makefile 中
    • 搜索目标文件失败:产生警告
      • 以文件名作为目标查找并执行对应规则
      • 当文件名对应的规则不存在时,最终产生错误

在这里插入图片描述

  • make 解释器在解析 makefile 时会试图包含test.txt,发现 test.txt 文件并不存在,于是,make 以 test.txt 为目标查找并执行对应的规则,所以首先输出的是 test.txt。如果不存在 test.txt 文件也不存在以 test.txt 为目标对应的规则,则 make 直接报错。
  • 通过在 include 前加上"-",消除文件不存在时产生的警告,如:-include filename,但这种做法也容易将一些 bug 隐藏。

在这里插入图片描述

make 通过 include 将 test.txt 的内容原封不动的搬到了 makefile 中,other 对应的规则也成了第一条规则对应的目标,make 默认情况下寻找第一个目标执行,所以输出 this is other

在这里插入图片描述

make 解释器一开始发现当前目录下没有 test.txt 文件,便去寻找以 test.txt 为目标的规则,找到之后,执行对应的命令,生成了一个 test.txt 文件,并向该文件中写入了 other : ;@echo this is other 这条规则,然后,make 解释器再次将这个新生成的文件内容原封不动的加载到 makefile 中,other 目标对应的规则成了第一条规则,make 默认情况下执行这个目标下的命令,因此,最终输出了 this is other

2.4 makefile 中命令的执行机制

  • 规则中的每个命令默认是在一个新的进程中执行(shell)
  • 可以通过接续符(;)将多个命令组合成一个命令
  • 组合的命令依次在同一个进程中被执行
  • set -e 指定发生错误后立即退出执行

2.4.1 问题

  • 下面的代码想要实现的功能?有什么问题?

在这里插入图片描述
在这里插入图片描述

  1. 在执行 mkdir test 命令时,make 会创建一个进程,执行完这条命令,进程终止。
  2. 接着该执行下一条命令 cd test 了,同样是先创建一个进程,然后执行完命令,进程终止。
  3. 最后,make又创建一个进程,执行 mkdir subtest 命令,但是这个进程的工作目录是当前目录,而没有进入到 test 目录,因此,在当前目录下创建了文件夹 subtest。而我们希望的是 subtest 文件夹是建立在 test 目录下。

2.4.2 加上连接符

在这里插入图片描述

3. 自动生成依赖关系的解决方案

3.1 初步方案

  • 通过 gcc -MM 和 sed 得到 .dep 依赖文件(目标的部分依赖)
    规则中命令的连续执行
  • 通过 include 指令包含所有的 .dep 依赖文件
    当 .dep 依赖文件不存在时,使用规则自动生成

在这里插入图片描述

  • 如何在 makefile 中将 .dep 文件组织到指定目录?
  • 当 include 发现 .dep 文件不存在时:
  1. 通过规则和命令创建 deps 文件夹。
  2. 将所有 .dep 文件创建到 deps 文件夹。
  3. .dep 文件中记录目标文件的依赖关系。

在这里插入图片描述

3.2 存在的问题

  • 为什么一些 .dep 依赖文件会被重复创建多次?
  • 问题本质分析:
  • deps 文件夹的时间属性会因为依赖文件创建而发生改变
  • make 发现 deps 文件夹比对应的目标更新
  • 触发相应的规则重新解析和命令的执行
    (对于 func.c 来说就是 deps/func.dep : deps func.c,make 记录下该规则后便去执行规则下的命令,生成依赖文件,接着再去推倒出 main 相关的规则并执行命令。当生成 deps/main.dep 时,deps 文件夹的时间戳也被更新了,因此,make 根据记录下来的规则发现,应该重新生成 func.dep,这就是为什么执行结果中出现了创建两次 func.dep的原因)

3.3 优化方案

使用 ifeq 动态决定 .dep 目标的依赖

在这里插入图片描述
在这里插入图片描述

3.4 进一步优化

  • 当 .dep 文件生成后,如果动态的改变头文件间的依赖关系,那么 make 可能无法检测到这个改变,进而做出错误的编译决策。
  1. 基于上一个实验代码,先 make 得到上一个实验的结果
  2. 接着新建 new.h,在 func.h 中包含 new.h 文件

在这里插入图片描述

  • 优化

在这里插入图片描述
在这里插入图片描述

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uuxiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值