Makefile的使用

        在Linux中使用 make 命令来编译程序,特别是大程序;而 make 命令所执行的动作 依赖于 Makefile 文件。以下是最简单的Makefile文件:

 首先,包含如下文件:

Makefile文件内容如下所示:

然后,直接执行 make 命令编译程序:

执行 make clean 命令 即可清除编译出来的结果:

        make命令 根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免已经编译过的、没有变化的程序,可以大大提高编译效率。

一、Makefile规则与示例讲解

1、规则

规则如下所示:

目标(target): 依赖(prerequiries)
<tab>命令(command)

需要特别注意,必须以Tab键进行缩进,不能以空格键缩进。

        如果 “依赖文件” 比 “目标文件” 更加新,那么执行 “命令” 来重新生成 “目标文件”

        命令执行的两个条件:依赖文件比目标文件新,或者是 目标文件没有生成

这里介绍两个函数:

(1)$(foreach var, list, text)

        简单地说,就是 for each var in list, change it to text

        其实就是 对 list 中的每一个元素,取出来赋给 var,然后把 var 改为 text 所描述的形式。

如:

objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d)    //最终 dep_files := .a.o.d .b.o.d

(2)$(wildcard pattern)

        pattern 所列出的文件是否存在,把存在的文件都列出来。

如:

src_files := $( wildcard *.c)    //最终 src_files 中列出了当前目录下的所有.c文件

2、示例讲解

(1)简单粗暴,效率低:

test : main.c sub.c sub.hgcc -o test main.c sub.c

(2)效率高,相似规则太多太啰嗦,不支持检测头文件:

test : main.o sub.ogcc -o test main.o sub.omain.o : main.cgcc -c -o main.o main.csub.o : sub.cgcc -c -o sub.o sub.cclean:rm *.o test -f

(3)效率高,精炼,不支持头文件检测:

test : main.o sub.ogcc -o test main.o sub.o%.o : %.cgcc -c -o $@ $<clean:rm *.o test -f

(4)效率高,精炼,支持头文件检测(但是需要手动添加头文件规则):

test : main.o sub.ogcc -o test main.o sub.o%.o : %.cgcc -c -o $@ $<sub.o : sub.hclean:rm *.o test -f

(5)效率高,精炼,支持自动检测头文件

objs := main.o sub.o
test : $(objs)gcc -o test $^# 需要判断是否存在依赖文件
# .main.o.d .sub.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))# 把依赖文件包含进来
ifneq ($(dep_files),)include $(dep_files)
endif%.o : %.cgcc -Wp,-MD,.$@.d -c -o $@ $<
clean:rm *.o test -f
distclean:rm $(dep_files) *.o test -f

二、通用Makefile的使用

1、解析

(1)make 命令的使用

        执行 make 命令时,它会去 当前目录下 查找名为 “Makefile” 的文件,并根据它的指示去执行操作,生成 第一个 目标。

        我们可以使用 “ -f ” 选项指定文件,不再使用名为 “Makefile” 的文件,比如:

make -f Makefile.buid

        我们也可以使用 “ -C ”选项指定目录,切换到其他目录里去,比如:

make -C a/ -f Makefile.buid

        我们可以指定目标,不再 默认生成 第一个 目标:

make -C a/ -f Makefile.buid other_target

(2)即时变量、延时变量、自动变量

  • 延迟赋值,在Makefile运行时才会被赋值
  • := 立即赋值,立即赋值是在真正运行前就会被赋值
  • ?= 空赋值,如果变量没有设置过才会被赋值
  • += 追加赋值,可以理解为字符串的加操作

        变量的定义语法如下所示:

A = xxx // 延时变量
B ?= xxx // 延时变量,只有第一次定义时赋值才成功;如果曾定义过,此赋值无效
C := xxx // 立即变量
D += yyy // 如果 D 在前面是延时变量,那么现在它还是延时变量;
// 如果 D 在前面是立即变量,那么现在它还是立即变量

        上面语句中,变量A是延时变量,它的值在使用时才展开、才确定。比如:

A = $@
test:@echo $A

        上述Makefile中,变量A的值在执行时才确定,它等于test,是延时变量。

        如果使用 “ A := $@ ”,这是立即变量,这时" $@ "为空,所以A的值就为 空。

自动变量:

        Makefile有很多自动变量,这里只介绍几个常用的,分别是 $<、$^、$@,其它的可以去参考Makefile文档。

$< 表示第一个依赖的文件,例如:

test: test.o test2.o  echo $<
test.o:
test2.o:

        最终的结果是打印了 test.o,也就是test第一个依赖。

$^ 表示所有依赖,例如:

test: test.o test2.o  echo $^
test.o:
test2.o:

        最终结果是打印了 test.o test2.o,是test全部的依赖。

$@ 表示目标,例如:

test: test.o test2.o  echo $@
test.o:
test2.o:

        最终结果打印的是 test,也就是Makefile中的目标。

(3)变量的导出(export)

        在编译时,我们会不断地使用 " make -C dir " 切换到其它目录,执行其他目录里的 Makefile。如果 想让某个变量的值在所有目录中都可见,要把它 export 出来。比如:

CC = $(CROSS_COMPILE)gcc

        上面的CC变量表示编译器,在整个过程中都是一样的。定义它之后,要使用 “ export CC ” 把它导出来。

(4)Makefile中可以使用shell命令

比如:

TOPDIR := $(shell pwd)

        这是一个即时变量,TOPDIR 等于 shell 命令pwd 的结果。

(5)在Makefile中怎么放置 第一个 目标

        执行 make 命令时,如果不指定目标,那么它默认是去生成 第一个 目标。

        所以 “ 第一个目标 ” ,位置很重要。有时候不太方便把 第一个目标 完整地放在文件前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖与命令。比如:

First_target: // 这句话放在前面
.... 		// 其他代码,比如 include 其他文件得到后面的 xxx 变量
First_target : $(xxx) $(yyy) // 在文件的后面再来完善command

(6)假想目标

        比如,我们的 Makefile 文件中有这样的目标:

clean:rm -f $(shell find -name "*.o")rm -f $(TARGET)

        如果当前目录下,恰好有名为 “ clean ” 的文件,那么执行 “ make clean ” 时,它就不会执行上面那些删除命令。

        这时,我们需要把 “ clean ” 这个目标,设置为 “ 假想目标 ”,这样可以确保执行 “ make clean ” 时,那些删除命令肯定可以得到执行。

        使用下面的语句 把 “ clean ” 设置为假想目标:

.PHONY : clean

2、通用Makefile的设计思想

(1)在Makefile文件中确定要编译的文件、目录

比如:

obj-y += main.o
obj-y += a/

        “ Makefile ” 文件总是被 “ Makefile.build ” 包含的。

(2)在 Makefile.build 中设置编译规则,有 3条规则

        怎么编译子目录?进入子目录编译

$(subdir-y):make -C $@ -f $(TOPDIR)/Makefile.build

        怎么编译当前目录中的文件?

%.o : %.c$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<

        当前目录下的 .o 和 子目录下的 build-in.o 要打包起来

built-in.o : $(cur_objs) $(subdir_objs)$(LD) -r -o $@ $^

(3)顶层 Makefile 中把顶层目录的 build-in.o 链接成 APP

$(TARGET) : built-in.o$(CC) $(LDFLAGS) -o $(TARGET) built-in.o

(4)情景演绎

使用情景:

./Makefile:

CROSS_COMPILE = 
AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nmSTRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdumpexport AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMPCFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/includeLDFLAGS := export CFLAGS LDFLAGSTOPDIR := $(shell pwd)
export TOPDIRTARGET := testobj-y += main.o
obj-y += sub.o
obj-y += a/all : start_recursive_build $(TARGET)@echo $(TARGET) has been built!start_recursive_build:make -C ./ -f $(TOPDIR)/Makefile.build$(TARGET) : built-in.o$(CC) -o $(TARGET) built-in.o $(LDFLAGS)clean:rm -f $(shell find -name "*.o")rm -f $(TARGET)distclean:rm -f $(shell find -name "*.o")rm -f $(shell find -name "*.d")rm -f $(TARGET)

./a/Makefile:


EXTRA_CFLAGS := -D DEBUG
CFLAGS_sub3.o := -D DEBUG_SUB3obj-y += sub2.o 
obj-y += sub3.o 

Makefile.build:

PHONY := __build__build:obj-y :=
subdir-y :=
EXTRA_CFLAGS :=include Makefile__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))ifneq ($(dep_files),)include $(dep_files)
endifPHONY += $(subdir-y)__build : $(subdir-y) built-in.o debugdebug:@echo cur_objs = $(cur_objs)@echo obj-y = $(obj-y)@echo subdir_objs = $(subdir_objs)@echo subdir-y = $(subdir-y)$(subdir-y):@echo subdir-y = $(subdir-y)make -C $@ -f $(TOPDIR)/Makefile.buildbuilt-in.o : $(cur_objs) $(subdir_objs)$(LD) -r -o $@ $^dep_file = .$@.d%.o : %.c$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<.PHONY : $(PHONY)

对上述情景的理解如下:

 整理,简化,大致是这样的:

        首先,在顶层执行 make,执行顶层Makefile,第一个目标为 all,而其第一个依赖为 start_recursive_build ,接着根据这个依赖,会去调用 make -C ./ -f $(TOPDIR)/Makefile.build ,可以替换为 make -C ./ -f $(shell pwd)/Makefile.build ,即在当前目录下,去执行  Makefile.build。

        因为 Makefile.build 脚本中包含了 include Makefile,所以这时候,会根据顶层Makefile,给 Makefile.build 中的 obj- 和 subdir-y 进行赋值,那么就有:

        obj-y = main.o sub.o a/
        cur_objs = main.o sub.o

        subdir-y = a
        subdir_objs = a/built-in.o

        第一个目标为 __build ,且其第一个依赖为 $(subdir-y),也就是a目录,先运行第一个依赖的命令,然后调用命令为 make -C $@ -f $(TOPDIR)/Makefile.build,替换后为 make -C a -f $(shell pwd)/Makefile.build。意思为:去子目录a下,运行Makefile.build。

        接着进入子目录运行 Makefile.build,同样根据子目录下的 Makefile 去给  obj- 和 subdir-y 进行赋值,由于没有子目录了,所以 subdir-y 为空,如下所示:

        obj-y = sub2.o sub3.o
        cur_objs = sub2.o sub3.o
        subdir_objs =
        subdir-y =

        第一个目标还是 __build,其依赖项两个:$(subdir-y)built-in.o。前者为空,因此不需要再次递归调用了。第二个目标是 built-in.o,找到其对应的依赖为$(cur_objs) $(subdir_objs),而subdir_objs又为空,因此不需要执行。

        由于已经子目录下已经执行完了,退回顶层目录文件夹下 的 Makefile.build 脚本中继续执行,这时,由于 __build 目标的第一个依赖 $(subdir-y) 已经执行结束,则开始执行第二个依赖 built-in.o ,而此时对应的两个依赖,$(cur_objs) $(subdir_objs),第一个生成了 %.o ,第二个为 a/built-in.o ,已经在上一步生成了。因此链接生成顶层的 built-in.o ,此时,目标生成,退出 Makefile.build 脚本,回到顶层的 Makefile 中。

        回到顶层的 Makefile ,继续执行第一个目标 all 的 第二个依赖 $(TARGET) ,而 $(TARGET) 的依赖 built-in.o 也生成了,执行最后的命令 $(CC) $(LDFLAGS) -o $(TARGET) built-in.o 就结束 顶层Makefile 脚本了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/232554.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【程序】STM32 读取光栅_编码器_光栅传感器_7针OLED

文章目录 源代码工程编码器基础程序参考资料 源代码工程 源代码工程打开获取&#xff1a; http://dt2.8tupian.net/2/28880a55b6666.pg3这里做了四倍细分&#xff0c;在屏幕上显示 速度、路程、方向。 接线方法&#xff1a; 单片机--------------串口模块 单片机的5V-------…

天猫数据分析-天猫查数据软件-11月天猫平台饮料市场品牌及店铺销量销额数据分析

今年以来&#xff0c;饮料是快消品行业中少数保持稳定增长的品类之一。 11月份&#xff0c;饮料市场同样呈现较好的增长态势。根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;今年11月份&#xff0c;天猫平台上饮料市场的销量为2700万&#xff0c;环比增长约42%&#xf…

水经微图Web新版发布

水经微图Web新版已经上线&#xff0c;在该版本中主要新增了态势箭头标绘、文本要素标注和显示网页气泡等功能。 在本文中&#xff0c;我们将为大家分享新增的功能项&#xff0c;以及原有功能作的一些优化等。 当前版本 当前版本号为&#xff1a;1.4.0-beta 如果你发现该版…

linux 操作系统(二)

1、linux用户和权限 1.1、用户分类&#xff1a; 在linux中的用户可以分为两类&#xff0c;一类是root用户&#xff0c;root用户拥有linux操作系统的最大的权限&#xff0c;另外一类就是普通用户&#xff0c;相比于root用户&#xff0c;普通用户的权限受到一定的限制&#xff0…

zookeeper:启动后占用8080端口问题解决

ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务。它为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配置维护、域名服务、分布式同步、组服务等。 我们经常在运行zookeeper服务时&#xff0c;不需要配置服务端口&#xff0c;…

电脑风扇控制软件Macs Fan Control mac支持多个型号

Macs Fan Control mac是一款专门为 Mac 用户设计的软件&#xff0c;它可以帮助用户控制和监控 Mac 设备的风扇速度和温度。这款软件允许用户手动调整风扇速度&#xff0c;以提高设备的散热效果&#xff0c;减少过热造成的风险。 Macs Fan Control 可以在菜单栏上显示当前系统温…

C#中的协变和逆变

这两个都是只能使用在接口和委托上 个人理解&#xff1a; 协变&#xff1a;出参&#xff0c;让基类使用范围变大&#xff0c;将父类/基类当作子类一样使用 --为什么这样规定呢&#xff1f; 我的理解&#xff1a;真正实现的是子类&#xff0c;子类拥有所有的方法&#xff0c;却…

让AIGC成为你的智能外脑,助力你的工作和生活

人工智能成为智能外脑 在当前的科技浪潮中&#xff0c;人工智能技术正在以前所未有的速度改变着我们的生活和工作方式。其中&#xff0c;AIGC技术以其强大的潜力和广泛的应用前景&#xff0c;正在引领着这场革命。 AIGC技术是一种基于人工智能的生成式技术&#xff0c;它可以通…

个微和企微,哪个做私域流量的优势更大?

个人微信和企业微信是目前最为常用的私域经营平台&#xff0c;那在功能和使用上都有哪些区别&#xff1a; 1、开通对象不同&#xff1a; 个人微信是个人用户&#xff0c;个人就可以申请开通使用&#xff1b; 企业微信则要由企业在官方网站申请开通&#xff0c;并完成实名认证…

企业微信无法给Gmail发邮件问题

问题说明 在使用企业微信给国外客户的Gmail邮箱发信件的时候&#xff0c;邮件一直被退信&#xff0c;退信内容如下&#xff1a; 发件人&#xff08;*******.cn&#xff09;域名的DNS记录未设置或设置错误导致对方拒收此邮件。 host gmail-smtp-in.l.google.com[142.251.175.2…

左右按钮实现滚动轮播Demo(js手搓版本)

提示&#xff1a;适用于当放置按钮空间区域有限&#xff0c;通过左右箭头实现有限空间放置更多的按钮的情形&#xff0c;自适应布局的简单Demo支持二次开发和改造 文章目录 效果图Demo源码解释说明总结 效果图 在该区域存在五个按钮&#xff0c;点击左边按钮向左边滚动&#xf…

MySQL数据库,触发器、窗口函数、公用表表达式

触发器 触发器是由事件来触发某个操作&#xff08;也包含INSERT、UPDATE、DELECT事件&#xff09;&#xff0c;如果定义了触发程序&#xff0c;当数据库执行这些语句时&#xff0c;就相当于事件发生了&#xff0c;就会自动激发触发器执行相应的操作。 当对数据表中的数据执行…

如何选择数字化转型顾问

在进行数字化转型时&#xff0c;第一步也是最重要的一步是深刻了解你的业务需求&#xff0c;这一基本流程涉及对企业的目标、挑战和抱负进行全面分析。必须提出关键问题&#xff1a;你通过数字化转型寻求哪些具体结果?主要目标是优化运营效率、提升客户体验&#xff0c;还是使…

VueDraggablePlus - 免费开源的 Vue 拖拽组件,支持 Vue2 / Vue3,还被尤雨溪推荐了

今天在网上看到尤雨溪推荐的这款拖拽组件&#xff0c;试了一下非常不错&#xff0c;同样推荐给大家。 VueDraggablePlus 是一个专为 Vue 打造的拖拽排序模块&#xff0c;基于 Sortablejs 封装&#xff0c;支持 Vue3 或 Vue 2.7&#xff0c;本月的 21 日&#xff0c;Vue 作者尤…

从企业的角度看待WMS仓储管理系统的集成

随着全球化和数字化的发展&#xff0c;企业面临着越来越复杂的商业环境。为了满足高效运营的需求&#xff0c;许多企业开始寻求更先进、更集成的解决方案来优化他们的仓储流程。WMS仓储管理系统作为一种重要的解决方案&#xff0c;在企业中发挥着关键的作用。本文将从企业的角度…

通过外包团队迅腾文化灵活管理企业资讯内容输出,助力企业方对外信息的及时性与准确性

通过外包团队迅腾文化灵活管理企业资讯内容输出&#xff0c;助力企业方对外信息的及时性与准确性 随着信息时代的快速发展&#xff0c;企业信息的及时性和准确性对于企业的成功至关重要。外包团队迅腾文化以其灵活的管理方式&#xff0c;为企业提供了高效、准确的企业资讯内容…

速度与稳定性的完美结合:深入横测ToDesk、TeamViewer和AnyDesk

文章目录 前言什么是远程办公&#xff1f;远程办公的优势 远程办公软件横测对象远程软件的注册&安装ToDeskTeamViewerAnyDesk 各场景下的实操体验1.办公文件传输及丢包率2.玩游戏操作延迟、稳定3.追剧画质流畅度、稳定4.临时技术支持SOS模式 收费情况与设备连接数总结 前言…

开关电源测试 | 如何测试开关电源峰值负载功率?

开关电源峰值负载功率测试方法 测试设备&#xff1a; 1.电子负载&#xff1a;根据负载的额定电压和电流来选择。 2.功率计&#xff1a;需要考虑电源的额定电压和电流、测试频率等。 3.示波器&#xff1a;需要考虑测试频率和带宽等。 测试步骤&#xff1a; 1.将电源调整到正常工…

数据结构:图解手撕B-树以及B树的优化和索引

文章目录 为什么需要引入B-树&#xff1f;B树是什么&#xff1f;B树的插入分析B树和B*树B树B*树分裂原理 B树的应用 本篇总结的内容是B-树 为什么需要引入B-树&#xff1f; 回忆一下前面的搜索结构&#xff0c;有哈希&#xff0c;红黑树&#xff0c;二分…等很多的搜索结构&a…

单播、多播、广播、组播、泛播、冲突域、广播域、VLAN概念汇总

1 引言 “多播”可以理解为一个人向多个人&#xff08;但不是在场的所有人&#xff09;说话&#xff0c;这样能够提高通话的效率。如果你要通知特定的某些人同一件事情&#xff0c;但是又不想让其他人知道&#xff0c;使用电话一个一个地通知就非常麻烦&#xff0c;而使用日常…