Makefile快速入门
如果我们要编译一个程序,就需要为每一个文件编写一句执行文件生成命令,很麻烦,不是吗?如果文件更多呢?是不是更加难以想象。所以为了解决这一问题,神奇的Makefile诞生了!

一个小问题:
假设现在有如下的一个程序:


  
如果我们要编译这个程序,就需要为每一个文件编写一句执行文件生成命令,很麻烦,不是吗?如果文件更多呢?是不是更加难以想象。所以为了解决这一问题,神奇的Makefile诞生了。


1. Makefile简介
Makefile简单来说,就是一个能够帮助我们实现“自动化编译“的一个工具。一个中大型 C/C++ 工程的源文件有成百上千个,它们按照功能、模块、类型分别放在不同的目录中,Makefile 文件定义了一系列规则,指明了源文件的编译顺序、依赖关系、是否需要重新编译等。一旦编写好了工程的Makefile文件,只需一个make命令,整个工程就开始自动化编译,完全不需要手动输入执行命令。


2. Makefile规则
在正式进入示例之前,我们先简单了解一下Makefile的规则:
target ... : prerequisites ...
[tab]command


target 也就是一个目标文件,可以是 Object File,也可以是执行文件,还可以是一个标签(Label)。 prerequisites 就是要生成这个target 所需要的文件或目标。 command 就是 make真正需要执行的命令。
注意:command必须是以tab键开头的。


3. 从一个示例入手
对于最开始的问题,我们可以编写一个这样的Makefile文件。


                                             图3-1


关于文件的命名,通常会命名为“Makefile“或”makefile“,执行时,只需在该目录下直接输入命令”make“,就可以生成可执行文件main。当然也可以使用其他文件名,比如: “Make.Linux” ,“Make.Solaris”,“Make.AIX”等,如果要指定特定的 Makefile,可以使用 make 的“-f”和“--file”参数,如:make -f Make.Linux 或 make --file Make.AIX。如果要删除执行文件和所有中间文件,只要执行”make clean“即可。


在这个 Makefile 中,目标文件(target)包含:执行文件main和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的 .c 文件和 .h 文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 main 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。
在定义好依赖关系后,后续一行定义了如何生成目标文件的操作系统命令,一定要以一个 Tab 键作为开头。make 并不管命令是怎么工作的,他只会执行所定义的命令。make 会比较 targets 文件和 prerequisites 文件的修改日期,如果 prerequisites 文件的日期要比 targets 文件的日期要新,或者 target 不存在的话,那么,make 就会执行后续定义的命令。


此处简单说明一下,clean 不是一个文件,它只不过是一个动作名字,就像C语言中的 lable 一样,其冒号后什么也没有,那么,make 就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在 make 命令后明显的指出这个lable 的名字。这样的方法非常有用,我们可以在一个 makefile 中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。


最终我们的编译效果如下:


                                                 图3-2


3.1 Makefile中的变量声明
在第一行中,首先我们声明了一个objects变量,可以将它简单理解为C语言中的宏。从示例中,我们可以看到,objects被重复使用了三次,本来这三处应该分别写六个[.o]文件,一旦我们加入一个新的文件,就需要修改三个地方,但此时,只需要在最开始添加即可。
反斜杠(\)是换行符的意思,便于Makefile的阅读。
对于不同的编译环境,我们有时候使用“cc“,有时候使用”gcc“甚至其他,为了便于修改,我们可以在最开始的时候,对命令进行变量声明。


3.2 Makefile的宏开关
在实际的工程中,我们总会遇到同一套代码下,由于产品型号的不同,所支持的功能也会有所不同这样的需求。如果每次因为需求的变更,都要去修改源码,这必然是非常麻烦且不可取的。因此我们推出了“宏开关“这样的概念,在示例main.c文件中,用一个宏来判断要执行哪段程序。
Makefile中,使用指令 –D 来声明一个宏。
CFLAGS += -DNUMBER
在接下来的执行指令中,加入CFLAGS,即代表编译时,执行宏打开的那些功能。


3.3 make的自动推导
GNU 的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的 make 会自动识别,并自己推导命令。只要 make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果 make找到一个 whatever.o,那么 whatever.c,就会是 whatever.o 的依赖文件。并且 gcc -c whatever.c 也会被推导出来, 所以我们的Makefile可以升级成如下状态:

                                        图3-3


4.模式规则和自动化变量
真正的Makefile远远不止图3-3所示的那样,高级的Makefile甚至可以同时适用给不同的工程。那也就意味着,在Makefile中,不能出现任何一个和源代码文件名有关的内容。也就引出了模式规则,这一概念。先来看看重新升级后的Makefile:

                                           图3-4


4.1模式规则
模式规则中,至少在规则的目标定义中要包含"%",否则,就是一般的规则。目标中的
"%"定义表示对文件名的匹配,"%"表示长度任意的非空字符串。例如:"%.c"表示以".c"结尾的文件名(文件名的长度至少为 3),而"s.%.c"则表示以"s."开头,".c"结尾的文件名(文件名的长度至少为 5)。
例如一个模式规则如下:
%.o : %.c
<command……>
其含义是,指出了怎么从所有的[.c]文件生成相应的[.o]文件的规则。示例中就是使用了该规则。


4.2 自动化变量
所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。
常用的自动化变量及说明:
$@指代当前目标,就是Make命令当前构建的那个目标。比如,make foo的 $@ 就指代foo
$< 指代第一个前置条件。比如,规则为 t: p1 p2,那么$< 就指代p1。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。示例中就是使用了这一特性。
$^ 指代所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,那么 $^ 就指代 p1 p2 。
$* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1。
还有一些其他自动化变量,此处不做赘述,如有需要,可以查阅相关资料。


4.3通配符
如果想定义一系列比较类似的文件,我们就可以使用通配符。make支持三个通配符:“*”,“?”和“[...]”。 通配符代替了你一系列的文件,如“*.c”表示所有后缀为 .c 的文件。此外,如果我们的文件名中有通配符,如:“*”,那么可以用转义字符“\”,如“\*”来表示真实的“*”字符,而不是任意长度的字符串。
在Makefile规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,语法为:
$(wildcard PATTERN...) 。


4.4 函数
在 Makefile 中可以使用函数来处理变量,函数调用后,函数的返回值可以当做变量来使用。函数调用时,以“$“来标识,语法如下:
$(<function> <arguments>)
或是
${<function> <arguments>}
<function>就是函数名,<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。
make所支持的函数不多,但已经足够我们使用。主要包括字符串处理函数、文件名操作函数、foreach函数、if函数、call函数、origin函数、shell函数、控制make的函数等。我们简单讲述一下几个常见的函数,其他不做详细讲述,如有需要,可查阅相关资料。
(1)patsubst
语法:$(patsubst <pattern>,<replacement>,<text>)
名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符
合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%”来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串。
示例:
$(patsubst %.c,%.o,x.c.c bar.c)
把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”
(2)subst
语法:$(subst <from>,<to>,<text>)
名称:字符串替换函数——subst。
功能:把字串<text>中的<from>字符串替换成<to>。
返回:函数返回被替换过后的字符串。
示例:
$(subst ee,EE,feet on the street),
把“feet on the street”中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”。
(3)sort
语法:$(sort <list>)
名称:排序函数——sort。
功能:给字符串<list>中的单词排序(升序)。
返回:返回排序后的字符串。
示例:$(sort foo bar lose)返回“bar foo lose” 。
备注:sort 函数会去掉<list>中相同的单词。


附言
关于makefile的学习,初级入门比较简单,但要真正的使用makefile,还需要进一步学习和积累。推荐初学者阅读《跟我一起写makefile》,能够帮助我们快速的了解makefile的用法规则,原理基础等。

版权声明:本文为V社区用户原创内容,转载时必须标注文章的来源(V社区),文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:v-club@hikrobotics.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
上一篇

用wireshark抓包分析GVCP协议入门

下一篇

10GigE:高带宽易实施的明智选择

评论请先登录 登录
全部评论 1
写的挺好的
2022-02-19 10:54:50 未知地区
回复
  • 1
Lv.0
1
关注
384
粉丝
77
创作
876
获赞
相关阅读
  • VM4.4更新亮点
    2024-04-12 浏览 0
  • 第二届启智杯—光伏电池片质检视觉方案设计
    2024-04-15 浏览 0
  • 第二届启智杯-锂电外壳外观检测3D视觉方案设计
    2024-04-15 浏览 0
  • 第二届启智杯-无监督异常检测算法
    2024-04-16 浏览 0
  • 双车联动调试案例-华工中试基地
    2024-04-28 浏览 0

请升级浏览器版本

您正在使用的浏览器版本过低,请升级最新版本以获得更好的体验。

推荐使用以下浏览器