您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
Makefile基础教程
 
   次浏览      
 2018-7-27
 
编辑推荐:
本文来自于CSDN,本文通过实验将介绍make的变量定义风格,变量的替换引用,环境变量、命令行变量等。

一、实验介绍--Makefile 变量

本次实验将介绍make的变量定义风格,变量的替换引用,环境变量、命令行变量、目标指定变量的使用及自动化变量的使用。

1.1 实验内容

1.不同的变量风格和赋值风格

2.变量的替换引用,环境变量、命令行变量的使用

3.目标指定变量的使用

4.自动化变量的使用

1.2 实验知识点

1.变量的定义及展开时机。

2.递归展开变量使用"="或define定义,在使用时展开。

3.递归展开变量的定义与书写顺序无关,但也会产生难于调试和函数重复调用的问题。

4.直接展开变量使用":="定义,在make读入当前行时立即展开。5.+=操作符可以对变量进行追加,展开方式与变量原始的赋值方式一致。6.?=操作符可以在变量未定义时进行赋值。

5.变量的替换引用可以将变量展开的内容进行字符串替换。

6.系统环境变量对makefile来说是可见的,但文件中的同名变量会覆盖环境变量,可以使用-e选项避免覆盖。

7.命令行变量比makefile中的普通变量具有更高的优先级,可以使用override关键字防止makefile中的同名变量被命令行指定变量覆盖。

8.目标指定变量仅在包括依赖项在内的上下文可见,类似于局部变量,优先级高于普通变量。

9.自动化变量可以根据具体目标和依赖项自动生成相应的文件列表。

1.3 实验环境

Ubuntu系统, GNU gcc工具,GNU make工具

1.4 适合人群

本课程难度为中等,适合已经初步了解 makefile 规则的学员进行学习。

1.5 代码获取

可以通过以下命令获取代码:

二、实验原理

依据 makefile 的基本规则进行正反向实验,学习和理解规则的使用方式。

三、开发准备

进入实验楼课程即可。

四、项目文件结构

五、实验步骤

5.1 make 的递归执行示例

5.1.1 抓取源代码

使用如下 cmd 获取 GitHub 源代码并进入相应章节:

5.1.2 递归展开式变量

makefile 变量就是一个名字,代表一个文本字符串。变量有两种定义方式:递归展开式变量和直接展开式变量。变量在makefile的读入阶段被展开成字符串。

递归展开式变量可以通过"="和"define"进行定义,在变量定义过程中,对其它变量的定义不会立即展开,而是在变量被规则使用到时才进行展开。

chapter9/style/目录下的makefile文件演示了递归展开式变量的定义和使用方式。

文件内容如下:

文件中recur规则用到3个变量,a1是直接定义字符串,a2引用后面才定义到的a3,a3则引用a1。

loop规则用到b1,b22个变量,二者相互引用。

进入style目录,测试recur规则:

终端打印:

可见a1 a2 a3的值是一致的,变量的展开与定义顺序无关。

再测试loop命令:

终端打印:

make 因为两个变量的无限递归而报错退出。

从上面测试可以看出递归展开式的优点:此变量对引用变量的定义顺序无关。缺点则是:多个变量在互相引用时可能导致无限递归。

除此之外,递归展开式变量中若有函数引用,每次引用该变量都会导致函数重新执行,效率较低。

5.1.3 直接展开式变量

直接展开式变量通过":="进行定义,对其它变量的引用和函数的引用都将在定义时被展开。

文件direct.mk将makefile中的"="替换为":=",重新执行recur和loop规则:

终端打印:

从测试结果可以看出,由于a2,b1都引用了尚未定义的变量,因此被展开为空。

使用直接展开式变量可以避免无限递归问题和函数重复展开引发的效率问题,并且更符合一般的程序设计逻辑,便于调试问题,因此推荐用户尽量使用直接展开式变量。

5.1.4 变量追加和条件赋值

使用+=赋值符号可以对变量进行追加,变量追加时的赋值风格与变量定义时一致,若追加的是未定义变量,则默认以递归展开式风格进行赋值。

使用?=赋值符号可以对变量进行条件赋值,若变量未被定义则会对变量进行赋值,否则不改变变量的当前定义。

append.mk文件演示了追加赋值和条件赋值的使用方式,内容如下:

dir 和recur规则演示了递归展开式变量和直接展开式变量使用追加赋值的区别。

def规则演示了未定义变量追加赋值的默认风格。

cond演示了条件赋值的使用。

分别执行四条规则:

终端打印:

请自行分析每一行打印与其原因。

实验过程如下图所示:

5.2 变量的替换

5.2.1 替换引用

对于已经定义的变量,可以使用"替换引用"对其指定的字符串进行替换。

替换引用的格式为$(VAR:A=B),它可以将变量VAR中所有A结尾的字符替换为B结尾的字符。

也可以使用模式符号将符合A模式的字符替换为B模式。

chapter9/rep/makefile演示了变量的替换引用,内容如下:

文件中分别对不同的变量进行替换引用和模式替换引用,进入rep目录并测试:

终端打印:

vari_b中的.o后缀被替换成了.c后缀,f.o.o被替换未f.o.c,这表明只有后缀会被替换,字符串的其它部分保持不变。

vari_c则是使用模式符号替换后缀,结果与vari_b一致。

vari_d使用模式符号将前缀f.o替换为f.c。

5.2.2 环境变量的使用

对于makefile来说,系统下的环境变量都是可见的。若文件中的变量名与环境变量名一致,默认引用文件中的变量。

文件envi.mk演示了变量CC与环境变量CC发生冲突时的执行情况:

文件定义一个CC变量并赋值为abc,执行终极目标时打印CC变量的内容。

我们先export一个环境变量CC,再执行envi.mk观察两个变量是否有区别:

终端打印:

说明makefile自定义变量优先级高于环境变量。我们也可以在makefile中取消CC变量的定义或者修改PATH变量定义看看会发生什么状况。

5.2.3 防止环境变量被覆盖

可以使用-e选项防止环境变量被同名变量覆盖,如上述实验加入-e选项:

终端打印:

5.2.4 命令行变量

与环境变量不同,在执行make时指定的命令行变量会覆盖makefile中同名的变量定义,

如果希望变量不被覆盖则需要使用override关键字。

override.mk文件演示了命令行参数的覆盖和override关键字的使用:

vari_a和 vari_c是递归展开式变量,vari_b和 vari_d是直接展开式变量,vari_e是未定义变量。

现在从命令行传入vari_a到vari_e并查看变量最终的展开值:

终端打印:

从打印可以看出无论哪种风格的变量,都需要使用override指示符才能防止命令行定义的同名变量覆盖。

同时,用override定义的变量在进行修改时也需要使用override,否则修改不会生效,验证方法如下:

终端打印:

可见命令行没有传入变量,但vari_c和vari_d仍然无法追加不用override指示符时的"+= zzz"。

实验过程如下图所示:

5.3 目标指定变量和模式指定变量

makefile 中定义的变量通常时对整个文件有效,类似于全局变量。除了普通的变量定义以外,还有一种目标指定变量,定义在目标依赖项处,仅对目标上下文可见。这里的目标上下文也包括了目标依赖项的规则。

目标指定变量还可以定义在模式目标中,称为模式指定变量。

当目标中使用的变量既在全局中定义,又在目标中定义时,目标定义优先级更高,但需注意:目标指定变量与全局变量是两个变量,它们的值互不影响。

chapter9/target/makefile演示了目标指定变量的用法,内容如下:

makefile中定义了vari_a和vari_b两个全局变量,目标all指定了一个同名的vari_a变量,模式目标pre_%指定了一个同名的`vari_b变量。

每个目标的规则中都打印它们能看到的vari_a和vari_b的值,大家可以根据前面所述的规则推测每个目标分别会打印什么信息。

进入target目录,执行make:

终端打印:

由于终极目标all指定了vari_a为"all_target",因此在整个目标重建过程中vari_a都以目标指定变量的形式出现。vari_b仅在模式目标pre_%中被定义,因此对pre_a和pre_b来说,vari_b为pat,但对file_%和all目标而言,vari_b是全局变量,展开后为def。

我们也可以单独以pre_a和file_c为目标,看看内容有什么区别:

终端打印:

再执行:

终端打印:

由于此时并非处于all目标的上下文中,所以all指定的vari_a变量失效,取而代之的是原有的值"abc",而pre_%指定了vari_b变量,所以对pre_a来说,vari_b变量依然是"pat"。

实验过程如下图所示:

5.4 自动化变量

在模式规则中,一个模式目标可以匹配多个不同的目标名,但工程重建过程中经常需要指定一个确切的目标名,为了方便获取规则中的具体的目标名和依赖项,makefile 中需要用到自动化变量,自动化变量的取值是根据具体所执行的规则来决定的,取决于所执行规则的目标和依赖文件名。

总共有七种自动化变量:

$@:目标名称

$%:若目标名为静态库,代表该静态库的一个成员名,否则为空

$<:第一个依赖项名称

$?:所有比目标文件新的依赖项列表

$^:所有依赖项列表,重名依赖项被忽略

$+:包括重名依赖项的所有依赖项列表

$*:模式规则或静态模式规则中的茎,也即"%"所代表的部分

chapter9/auto/makefile 演示了七种自动化变量的用法,文件内容如下:

终极目标all的依赖项包括pre_a pre_b pre_c lib和库文件libadd.a,其中重复包含了一次pre_a依赖项。

模式规则pre_%利用静态模式依赖于对应的depen_%规则,打印匹配到的茎,并生成目标文件,库文件规则打印$%并打包生成libadd.a。

由于此处会用到$(CC)进行编译,而我们之前将环境变量CC赋值为"def",现在需要将其修改回来:

现在进入 auto 目录并执行 make:

终端打印:

make首先重建pre_a pre_b pre_c依赖项,并打印匹配到的茎a b c,接下来重建lib规则,libadd.a在重建过程中打印$%,从打印和打包命令可以看出$%展开后仅为add.o这一项文件,但静态文件目标会依据给定的文件列表展开多次。最后,make执行终极目标all的命令列表,分别打印其自动化变量,并生成all文件。

请大家仔细观察不同规则下自动化变量的变化。由于这是初次建立终极目标,因此$?得到的依赖项列表是全部的依赖项。使用touch命令更新pre_a pre_b再次测试:

终端打印:

由于pre_a pre_b被手动更新过,现在打印的$?内容为pre_a pre_b。

上述七个自动化变量除了直接引用外,还可以在其后增加D或者F字符获取目录名和文件名,

如:$(@D)表示目标文件的目录名,$(@F)表示目标文件的文件名。这种用法非常简单,也适用于所有的自动化变量,请大家自行实验测试。

实验过程如下图所示:

六、实验总结

本本次实验介绍了make的变量定义风格,变量的替换引用,环境变量、命令行变量、目标指定变量的使用及自动化变量的使用。

七、课后习题

请自行设计实验测试自动化变量的目录名和文件名的获取。

   
次浏览       
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程