【Linux + Makefile】Makefile的高阶用法:解决C文件包含的头文件修改了,但C文件不重新编译的问题

【Linux + Makefile】Makefile的高阶用法:解决C文件包含的头文件修改了,但C文件不重新编译的问题makefile的聪明之处。

大家好,又见面了,我是你们的朋友全栈君。

在上一篇 《【Linux + Makefile】十分钟教你学会Makefile的FORCE 》文章的最后,笔者就FORCE的用法在一个示例工程中使用,提出了一个问题:为何build_info.h每次都是新生成的(有修改过),而main.c又是有include “build-info.h”,但main.c却不是每次都重新编译呢?这个到底是不是违反了Makefile的基本规则呢?本文将给你答案,通过阅读本文,你将了解到以下内容:

  • 如何保证在C文件中包含的头文件修改了的时候,C文件每次都会被重新编译?

为了更好地展示上诉描述的问题,我们将之前的示例工程稍微复杂化一点点:

整个工程有3个.c文件,a.c/b.c/main.c,其中main.c会调用a.c/b.c中的两个接口,同时main.c会include头文件build_info.h;这个build_info.h每次编译都会重新生成,按照我们之前的写法,我们Makefile可能就是这样:

SHELL           = /bin/bash #指定shell使用/bin/bash,否则echo -e可能会出问题
ECHO            = echo
BIN             = test
BUILG_INFO_H    = build_info.h
SRC-C-y         += a.c
SRC-C-y         += b.c
SRC-C-y         += main.c
SRC-O           = $(patsubst %.c, $(O)%.o, $(SRC-C-y))

all: gen_build_info $(BIN)

clean: 
    rm -rf $(SRC-O) $(BIN) $(BUILG_INFO_H)

$(BIN) : $(SRC-O)
    gcc -o $(O)"$@" $(SRC-O)
	
%.o : %.c
    gcc -c "$<" -o "$@"
	
gen_build_info: $(BUILG_INFO_H)

$(BUILG_INFO_H): FORCE     #强制生成build_info.h
    @$(RM) $@
    @$(ECHO) '  GEN     $@'
    @$(ECHO) -e   " #ifndef __BUILD_INFO_H__\n"\
				"#define __BUILD_INFO_H__\n"\
				"#define APP_TIME        	\"`date "+%Y-%m-%d %H:%M:%S"`\"\n"\
				"#endif"  > $@

FORCE:
.PHONY: FORCE

执行make,我们会发现,跟我们的预期不一样:它虽然会每次都生成build_info.h,但是main.c包含了build_info.h却不会每次都重新编译。这个问题发生的原因,我们来分析下:

在我们的Makefile规则中,main.o只依赖于main.c (Makefile 第18-19行),而在第二次执行make的时候,main.c显然并没有被修改,所以main.o不会重新生成,自然可执行文件就不会重新生成。这里的问题根源在于,main.c它是依赖于build_info.h的,而这个依赖关系并没有体现在Makefile中,所以整个编译流程达不到我们的预期想法。我们尝试下,将main.c的依赖头文件也写入到Makefile中,怎么实现呢?

恰好,GCC给了我们强大的支持,它有个非常有用的选项 -MD -MF,它可以在生成一个.o的同时也生成它的依赖文件列表,修改后的Makefile如下所示:

SHELL           = /bin/bash #指定shell使用/bin/bash,否则echo -e可能会出问题
ECHO            = echo
BIN             = test
BUILG_INFO_H    = build_info.h
SRC-C-y         += a.c
SRC-C-y         += b.c
SRC-C-y         += main.c
SRC-O           = $(patsubst %.c, $(O)%.o, $(SRC-C-y))
SRC-C-DEPS      = $(patsubst %.c, $(O).%.o.d, $(SRC-C-y))  ## 由 a.c ==> .a.o.d

all: gen_build_info $(BIN)

clean: 
    rm -rf $(SRC-O) $(BIN) $(BUILG_INFO_H) $(SRC-C-DEPS)

$(BIN) : $(SRC-O)
    gcc -o $(O)"$@" $(SRC-O)
	
%.o : %.c
#	生成xxx.o的时候,同时生成它的依赖列表,放在文件.xxx.o.d中
	gcc -c "$<" -o "$@" -MD -MF "$(dir $@).$(notdir $@).d" -MT "$@"
	
gen_build_info: $(BUILG_INFO_H)

$(BUILG_INFO_H): FORCE     #强制生成build_info.h
    @$(RM) $@
    @$(ECHO) '  GEN     $@'
    @$(ECHO) -e   " #ifndef __BUILD_INFO_H__\n"\
				"#define __BUILD_INFO_H__\n"\
				"#define APP_TIME        	\"`date "+%Y-%m-%d %H:%M:%S"`\"\n"\
				"#endif"  > $@

FORCE:
.PHONY: FORCE

# 在Makefile末尾强制包含这些依赖文件
-include $(SRC-C-DEPS)

测试结果如下所示:

【Linux + Makefile】Makefile的高阶用法:解决C文件包含的头文件修改了,但C文件不重新编译的问题

再次执行make,多试几次,一样的结果。

【Linux + Makefile】Makefile的高阶用法:解决C文件包含的头文件修改了,但C文件不重新编译的问题

由上可知,经过改造后的Makefile是实现了我们的需求,每次build_info.h重新生成,导致main.c包含了build_info.h也会重新编译,而a.c和b.c没有被修改,所以在未执行make clean的情况下,a.c和b.c是不会被重新编译的,每次都是仅仅main.c被再次编译,从而重新生成新的test可执行文件。这样就是已经达到了【当C文件包含的头文件修改了的时候,C文件必须重新编译】的目的。


以上就是关于Makefile的高阶用法,基本满足了我们日常工程实践的需求。如果你对该Makefile有疑问,欢迎在评论席提出你的疑问,博主很乐意为你解答。

版权申明:本文为博主原创文章,转载请注明出处!【Linux + Makefile】Makefile的高阶用法:解决C文件包含的头文件修改了,但C文件不重新编译的问题_架构师李肯-CSDN博客

原创作者:recan

电子邮箱:recan.szu@foxmail.com


延伸阅读:

【Linux + Makefile】十分钟教你学会Makefile的FORCE

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/131588.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 计算机操作系统学习笔记「建议收藏」

    计算机操作系统学习笔记「建议收藏」第1章计算机系统概述 1.1操作系统的基本概念 1.1.1操作系统的概念 操作系统是计算机系统中最基本的系统软件。 操作系统(OperatingSystem,OS)是指控制和管理整个计算机系统的硬件与软件资源,合理地组织、调度计算机的工作与资源的分配,进而为用户和其他软件提供方便接口与环境的程序集合。 1.1.2操心系统的特征 操作系统的基本特征包括:并发、共享、虚拟和异步。并发和共享是操作系统两个最基本的特征,两者之间互为存在的条件。​ 1.并发(C

    2022年6月7日
    30
  • jsonobject详解_object转json字符串

    jsonobject详解_object转json字符串JSON(JavaScriptObjectNotation的首字母缩写)是一种轻量级数据交换格式,最常用于客户端-服务器通信。它既易于读/写,又与语言无关。JSON值可以是另一个JSON对象,数组,数字,字符串,布尔值(true/false)或null。也是后端开发中经常要接触的一个一个类,将要数据封装成json然后传递给前端。只要我们导入如下maven依赖即可使用由阿里巴巴发布的…

    2022年9月22日
    4
  • sklearn库的使用_导入turtle库的方法

    sklearn库的使用_导入turtle库的方法Sklearn库是基于Python的第三方库,它包括机器学习开发的各个方面。机器学习的开发基本分为六个步骤,1)获取数据,2)数据处理,3)特征工程,4)机器学习的算法训练(设计模型),5)模型评估,6)应用。机器学习的算法一般分为两种:一种既有目标值又有特征值的算法称之为监督学习,另一种只有特征值的算法称之为无监督学习。而监督学习还可以继续细分为分类算法和回归算法。1)获取数据⑤Sklearn中获取数据集使用的包为Sklearn.datasets,之后可以接load_*和fetch_*从Skle

    2022年10月7日
    2
  • webpack基本配置项_webpack配置文件详解

    webpack基本配置项_webpack配置文件详解前言上篇我们已经配置好了本地开发服务器,但是配置的相对比较凌乱,一个文件中有些是开发时用到的配置,有些是生成时用到的配置,有些是开发和生成都要用到的配置,所以我们这里把环境分为3个环境webpac

    2022年8月7日
    10
  • mybatis返回对象_存储过程不能返回结果

    mybatis返回对象_存储过程不能返回结果论MyBatis返回结果集_返回实体类还是Map在更多的了解mybatis后发现不单单通过实体类可以直接返回数据,还可以直接返回一个Map结果集(resultType="java.util.Map"),如果是多条数据则返回一个List&lt;Map&lt;String,Object&gt;&gt;结果集。很多人会觉得发现,直接返回一个Map的话太方便了,什么映射什么的全都不用管,只用在sql书…

    2022年10月4日
    2
  • radis简单理解

    radis简单理解radis简单理解

    2022年4月23日
    83

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号