makefile生成*.d依赖文件,解决“只修改.h头文件,包含了该头文件的.c文件不重新编译的问题”

makefile生成*.d依赖文件,解决“只修改.h头文件,包含了该头文件的.c文件不重新编译的问题”

  最近在使用makefile的过程中发现,当我只更改.h文件,不改任何.c文件的情况下,执行编译操作。.h的改动不会被编译器识别,也就没有编译到最终的可执行文件中。
  接下来,会通过一个简单的demo,来说明.h文件的改动无法被编译器识别的原因,以及如何解决此问题。
  demo中一共包含4个源文件,printHello.h,printHello.c,main.c,config.h。其目的是通过配置config.h中的宏定义,来控制main.c中的输出。源文件如下:

  printHello.h

//printHello.h
#ifndef _PRINTHELLO_H_
#define _PRINTHELLO_H_

void printHello1();
void printHello2();
#endif

  printHello.c

//printHello.c
#include <stdio.h>
#include "printHello.h"

void printHello1() {
	printf("hello1\n");
}

void printHello2() {
	printf("hello2\n");
}

  main.c

//main.c
#include "printHello.h"
#include "config.h"

void main() {
#ifdef HELLO1
	printHello1();
#endif
#ifdef HELLO2
	printHello2();
#endif

}

  config.h

//config.h
#define HELLO1
#define HELLO2

  makefile的代码如下:

CC = gcc
output_path = output
all_objects := $(output_path)/main.o
all_objects += $(output_path)/printHello.o

printHello: $(all_objects)
	$(CC) -o $@ $^
	
$(all_objects):$(output_path)/%.o:%.c
	$(CC) -c $< -o $@	

.PHONY: clean
clean:
	-rm printHello.exe
	del /Q /S output\*

  在接下来的操作中,我会通过改动config.h中的宏定义,来控制main.c中的输出。
  首先,config.h配置如下:

//config.h
#define HELLO1
#define HELLO2

在这里插入图片描述

  然后更改config.h中的宏定义,如下:

//config.h
//#define HELLO1
#define HELLO2

在这里插入图片描述
  我们预期的结果是,只输出hello2.
  但是我们发现,在更改了config.h后,再次运行make时,包含了config.h的main.c并没有被重新编译。所以最后我们的改动,没有更新的到最终的可执行文件中。
  分析原因:
  makefile中的目标文件更新的规则是:如果依赖文件的修改时间比目标文件的修改时间新,那么就会执行后面的命令来生成目标文件。
  实际上,main.o的依赖文件应该是:main.c printHello.h config.h。但在我们前面的makefile中,main.o的依赖文件只有main.c(makefile的9~10行),config.h并没有体现在我们的依赖关系中,所以编译器识别不了config.h的修改。所以我们的优化思路就应该是,想办法在makefile中体现完整的依赖关系。
  解决方案:
  GCC给我提供了自动生成依赖关系的方法。就是在生成目标文件的过程中,自动生成依赖关系文件(*.d)。不过生成的这些依赖关系文件也需要我们将其添加到makefile中。
  自动生成依赖文件的方法主要是给gcc编译器传递相关的参数。
参数定义:

  1. -M
    生成文件的依赖关系,同时也把一些标准库的头文件包含了进来。本质是告诉预处理器输出一个适合 make 的规则,用于描述各目标文件的依赖关系。对于每个源文件,预处理器输出 一个 make 规则,该规则的目标项 (target) 是源文件对应的目标文件名,依赖项 (dependency) 是源文件中 “#include” 引用的所有文件,生成的规则可以是单行,但如果太长,就用’'换行符续成多行。规则显示在标准输出,不产生预处理过的 C 程序。
    注意:该选项默认打开了 -E 选项, -E 参数的用处是使得编译器在预处理结束时就停止编译
  2. -MM
    生成文件的依赖关系,和 -M 类似,但不包含标准库的头文件。
  3. -MD
    等同于 -M -MF File,但是默认关闭了 -E 选项。其输出的文件名是基于 -o 选项,若给定了 -o 选项,则输出的文件名是 -o 指定的文件名,并添加 .d 后缀,若没有给定,则输入的文件名作为输出的文件名,并添加 .d 后缀,同时继续指定的编译工作。
    注意:-MD 不会像 -M 那样阻止正常的编译任务,因为它默认关闭了 -E 选项。比如命令中使用了 -c 选项,其结果要生成 .o 文件。若使用了 -M 选项,则不会生成 .o 文件,若使用的是 -MD 选项,则会生成 .o 文件
  4. -MF File
    当使用了 “-M” 或者 “-MM” 选项时,则把依赖关系写入名为 “File” 的文件中。若同时也使用了 “-MD” 或 “-MMD”,“-MF” 将覆写输出的依赖文件的名称,File中可以包含路径,使得依赖文件可以输出到指定的路径中 。
    例如:
gcc -MD -MF main.d main.c

  -MD所输出的关于main.c的依赖关系的文件,输出在main.d中。

  1. -MMD
    类似于 “-MD”,但是输出的依赖文件中,不包含标准头文件
  2. -MT Target
    在生成的依赖文件中,指定依赖规则中的目标

导入依赖文件:
  makefile中可以使用include关键字把其他文件包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:

include <filename>

可以在include前加一个减号-。如:

-include <filename>

其表示,无论include过程中出现什么错误,都不要报错继续执行。

  修改后的makefile:

CC = gcc
output_path = output
all_objects := $(output_path)/main.o
all_objects += $(output_path)/printHello.o

all_o_depds = $(patsubst %.o, %.o.d, $(all_objects))

printHello: $(all_objects)
	$(CC) -o $@ $^
	
$(all_objects):$(output_path)/%.o:%.c
#	$(CC) -c $< -o $@	
	$(CC) -MMD -MF $(output_path)/$(notdir $@).d -MT $@ -c $< -o $@

-include $(all_o_depds)

.PHONY: clean
clean:
	-rm printHello.exe
	del /Q /S output\*

  编译演示:
  首先,config.h配置如下:

//config.h
#define HELLO1
#define HELLO2

在这里插入图片描述
  然后更改config.h中的宏定义,如下:

//config.h
//#define HELLO1
#define HELLO2

在这里插入图片描述
  我们可以看到,在只更改了config.h后,运行make时,编译器重新生成了main.o目标文件,所以我们对.h文件的修改,就更新到了最后的可执行文件中。

  最后,介绍下demo中的文件结构:
在这里插入图片描述
  makefile会把编译中生成的目标文件(*.o)和依赖文件(*.d)放到output文件夹下。
在这里插入图片描述
其中main.o.d是main.o的依赖文件。其中的内容如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值