前一阵子,看了linux驱动程序中makefile变量的写法,有些东西没搞清楚,所
以索性就想把关于这块的内容搞明白,在这里感谢Dragonfly,他给我推荐了
一篇好文章,看了之后,豁然开朗,写点心得,希望大家喜欢。
原文见这里:
http://www.gnu.org/software/make ... r/make_6.html#SEC65
如果觉得英文烦,就听我先给各位侃侃吧,没按字翻译,写了点觉得有用的东西。
一. 为什么使用变量
变量在makefile中用来代表一个字符串,用来表示
1. 一系列文件的名字
2. 传递给编译器的参数
3. 需要运行的程序
4. 需要查找源代码的目录
5. 你需要输出信息的目录
6. 你想做的其它事情。[/list:u]
说白了,这有些类似于编程语言中的宏。
二. 定义变量的方式和建议
变量的名字是大小新敏感的,从大的方面来说,makefile中的变量被分为两种,
一个是用=来定义的,老外叫right-hand sides of variable,另外一种是用
define关键字定义的,叫做bodies of variable。先简单说这些,后面详述。
传统上使用大写字母为变量命名,但是GNU推荐使用小写字母作为makefile内部
使用的变量的名字,并用大写字母定义隐式规则中的参数或在命令行中允许用户
重新定义的参数。
三. 基本的变量引用
用$(name)或${name}来引用一个变量,所以当你要表示一个$符号时,你要使用$$。
当你只使用一个字母作为变量的名字时,你可以用$name来引用这个变量,不过GNU
并不推荐这样做,因为这种方式通常用来引用自动变量(Automatic Variables)。
四. 两种风格的变量定义
GNU有两种定义变量的方式,它们的不同体现在定义它们的风格和他们被展开的方式。
第一类叫做递归展开变量(Recursively Expanded Variable)。用=或define关键字都
可以定义这种变量,如果变量的定义引用了其它的变量,那么引用会一直展开下去,
直到找到被引用的变量的最新的定义,并以此作为改变量的值返回。例如:
[code:1]foo = $(bar)
bar = $(ugh)
ugh = Huh?[/code:1]
$(foo)的值究竟是什么呢? $(foo)被展开成$(bar),$(bar)被展开成$(ugh),最终$(ugh)
被展开成“Huh?”,那么$(foo)的值就是“Huh?”这种类型的变量是所有其他make工具支持
的变量类型。它有着自己的有点和缺点:
优点:
它可以向后引用变量
缺点:
1).你不能对该变量进行任何扩展,例如
CFLAGS=$(CFLAGS) -O
会造成无限循环展开,所有对于递归展开变量,这样做是不允许的。
2).如果makefile中的某个变量调用了某些函数,那么在变量被展开的每一次,函数都会被调用,
说的好些,无非是make慢点,更坏的情况是一些 shell函数和通配符的重复调用的结果往往
是难以预知的。
最后,用一个例子说明上面的问题。用这个makefile试一下就明白了(分别开启和注销掉第三行,
你就能看到结果)
[code:1]bar = test
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all: ;echo foo[/code:1]
为了解决这些问题,GNU定义了另一中其它风格的变量,叫做简单扩展变量(Simply Expanded Variables)
简单扩展变量用符号:=来定义,用这种方式定义的变量,会在变量的定义点,按照被引用的变量的当前值
进行展开。同样写个例子,测试一下,这东西用文字表达太困难了
[code:1]m := mm
x := $(m)
y := $(x) bar
x := later
all:;echo $(x) $(y)[/code:1]
这种定义变量的方式更适合在大的编程项目中使用,因为它更像我们一般的编程语言。
其实,GNU还对makefile中定义变量的方式进行了第三种扩展,用?=定义变量,它的含义是如果变量还没定义
就定义它,例如 FOO ?= Am I defined?,那么,它相当于下面的代码片断
[code:1]ifeq ($(origin FOO), undefined)
FOO = Am I defined?
endif[/code:1]
这种写法可以用于为用户提供默认的选项,即如果用户不定义,就提供给他默认的。另外,要说明的是把变量
定义成NULl,也是定义了变量,此时?=将不再起作用。
注意:
最后要说的是,无论我们使用那种风格定义变量,都不要在定义变量的行后面加上随机数目的空格,之后写注释,
这个我们想象的是不一样的,例如
ml = magic_linux
和
ml = magic_linux # My favourite linux distribution
这是两个完全不同的变量,其中第一个ml的值是magic_linux,而后一个的值是‘magic_linux ’,所以除非
你有意要定义末尾带有空格的变量,否则不要在定义变量行末尾随便添加空格和注释。
五. 高级的变量引用方法
有两种方法: 1. 替换变量引用 2. 计算变量的值(更确切的说是推导)
第一种
定义方法: $(var: a=b) ${var: a=b}
意义:把其变量a的值中,每一个词的最后一个字幕换成b
例如:
[code:1]foo := a.o b.o c.o
bar := $(foo:.o=.c)
all:;echo $(bar)[/code:1]
这时,bar的值等于a.c b.c c.c
另外,我们还可以用bar := $(foo:%.o=%.c)的形式
第二种
这是一种高级的makefile编程技术,一般我们很少使用它,但是它并不难,先看个例子
[code:1]x = y
y = z
a := $($(x))[/code:1]
此时,a的值是什么呢?答案是z。因为内层的$(x)=y,而外层的$(y)=z,也就是说a的值是通过$(x)计算出来的,所以
叫做计算变量的值。当然嵌套可是有很多层,自己试试吧,不多说了。在嵌套变量引用的时候,还可以包含函数调用,
例如:
[code:1]x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))[/code:1]
一番推导,a的值等于Hello
整个变量的推导过程中可以涉及到多个变量,这样,一个变量就可以有多个字面值,例如:
[code:1]a_dirs := dira dirb
1_dirs := dir1 dir2
a_files := filea fileb
1_files := file1 file2
ifeq "$(use_a)" "yes"
a1 := a
else
a1 := 1
endif
ifeq "$(use_dirs)" "yes"
df := dirs
else
df := files
endif
dirs := $($(a1)_$(df)) [/code:1]
根据usa_a和use_dirs这两个两个变量的值,dirs可以有不同的值
另外,推导变量值还可以用在替换变量引用中,例如:
[code:1]a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)[/code:1]
根据上面a1值的不同,source可以等于a.c b.c c.c或1.c 2.c 3.c
这种内嵌变量的限制是你不能把函数调用作为定义变量的一种方式,举个例子
[code:1]ifdef do_sort
func := sort
else
func := strip
endif
bar := a d b g q c
foo := $($(func) $(bar)) [/code:1]
foo的是什么呢?它是sort a d b g q c或strip a d b g q c,而并不是我们想象的把a d b g q c作为参数
传递给sort或strip
你还可以把推导出来的变量名作为变量的左值
[code:1]dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c) [/code:1]
最后要说明的是,要注意把递归扩展变量和嵌套推广变量区分开。
六. 变量得到值的办法
1) 在你运行make的时候覆盖变量的值,例如make CFLAGS='-g -O'
2) 在makefile中用上面说的方式为变量赋值
3) 设定的环境变量可以在makefile中成为变量
4) GNU定义的一些自动变量,详见http://www.gnu.org/software/make ... make_10.html#SEC111
5) 一些变量有固定的初始值,详见http://www.gnu.org/software/make ... make_10.html#SEC106[/list:u]
七. 为变量添加值
你可以通过+=为已定义的变量添加新的值,例如
[code:1]objects = main.o foo.o bar.o utils.o
objects += another.o[/code:1]
看上去,+=有些和下面的代码类似
[code:1]objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o[/code:1]
但是,它们还是存在这一些不同
当变量从前没有被定义过, +=和=是一样的,它定义一个递归展开的变量,但是,当变量已经有定义的时候,+=只是简单
的进行字符的添加工作。
如果起初你用:=定义变量,那么+=只是利用变量的当前值进行添加,这和我们的直觉是一样的,例如:
variable := magic_
variable += linux
此时variable的值就是magic_linux
如果起初用=定义变量,+=的行为就变得有些古怪,它并不会在使用+=的地方马上进行变量展开,而是会把展开工作推后,
直到它找到最后变量的定义,这和=定义变量的行为是类似的,但是总觉得不合直觉,例如:
[code:1]var = I love
variable = linux
var += $(variable)
variable = magic
all:;echo $(var)[/code:1]
当你使用var := I love和上面的情况作对比的时候,这种差别就明显了。这种定义方式在当变量中引用了其他的变量时是
很有用的。例如:
[code:1]CFLAGS = $(includes) -O
...
CFLAGS += -pg # enable profiling[/code:1]
由于CFLAGS是递归扩展的,所以make处理CFLAGS时,并不会对其进行扩展,所以只要在使用CFLAGS前,把include定义就好了。
看似我们可以用CFLAGS := $(CFLAGS) -O来完成上面的任务,但是他们之间仍有差别,这会使CFLAGS变成一个简单扩展型的变
量,如果此时include尚未定义,整个CFLAGS就会变成-O -pg,而我们用+=是想把CFLAGS设定成$(include) -O-pg,这显然和
我们想象的有差距。
八. 使用override关键字
一般情况下,如果你是用命令行参数定义一个变量的话,在makefile中的定义就会被忽略,如果你想让你的定义仍然有效,就要
使用override关键字,向下面这样:
override variable = magic 或 override variable := magic
此时,用户在命令行中指定的值就会被忽略,如果你想在用户指定的命令行后面添加上自己的文字,用下面的方法
override variable += magic
这个东西的使用动机就是,你可以为用户指定一些你默认想让他们使用的选项,例如:
override CFLAGS += -g
九. 使用define定义变量
define可以定义一个代表多行指令的变量,例如
[code:1]define two-lines
echo foo
echo $(bar)
endef[/code:1]
它相当于
[code:1]two-lines = echo foo; echo $(bar)[/code:1]
十. 为特定目标的指定变量值
我们可以在编译特定的目标文件的时候,为变量设定特殊的值,例如:
[code:1]prog : CFLAGS = -g
prog : prog.o foo.o bar.o [/code:1]
在这个依赖关系中,编译prog时,就会为编译参数加上-g选项。当然你也可以使用override关键字
[code:1]prog : override CFLAGS = -g
prog : prog.o foo.o bar.o [/code:1]
十一. 为特定样式的文件指定变量值
我们还可以为生成特定各式的文件指定特殊的编译参数,例如
%.o : CFLAGS = -O
这样,所有生成.o的编译参数都会被加上-O的选项。
相关文章
- SQL Server中变量的声明和使用方法
- maven pom文件的parent节点的元素中不能使用properties里面的变量
- 【Linux基础及shell脚本】Shell脚本中变量的使用
- PySpark安装及WordCount实现(基于Ubuntu)-安装Java: PySpark需要Java运行环境。你可以使用以下命令安装OpenJDK: sudo apt update sudo apt install default-jre default-jdk 安装Scala: PySpark还需要Scala,可以使用以下命令安装: sudo apt install scala 安装Python: 如果你的系统没有Python,可以安装Python 3: sudo apt install python3 安装Apache Spark: 下载并解压Apache Spark。你可以在Apache Spark官网下载最新版本的Spark。 wget https://downloads.apache.org/spark/spark-x.y.z/spark-x.y.z-bin-hadoopx.y.tgz tar -xzvf spark-x.y.z-bin-hadoopx.y.tgz 将 spark-x.y.z-bin-hadoopx.y替换为你下载的实际版本。 设置环境变量: 在 .bashrc或 .zshrc文件中设置Spark和PySpark的环境变量: export SPARK_HOME=/path/to/spark-x.y.z-bin-hadoopx.y export PATH=$PATH:$SPARK_HOME/bin export PYSPARK_PYTHON=python3 记得使用实际的Spark路径。 安装PySpark: 使用pip安装PySpark: pip install pyspark 实现WordCount:
- C#委托(delegate)的常用方式- 委托的定义 // 委托的核心是跟委托的函数结构一样 public delegate string SayHello(string c); public delegate string SayHello(string c);:定义了一个公共委托类型 SayHello,该委托接受一个 string 类型的参数 c,并返回一个 string 类型的值。 Main 方法 static void Main(string args) { // 本质上其实就是把方法当作委托的参数 SayHello sayC = new SayHello(SayChinese); Console.WriteLine(sayC("欢迎大家")); SayHello sayE = new SayHello(SayEgnlish); Console.WriteLine(sayE("Welcome to")); // 简单的写法:必须类型一样 SayHello s1 = SayChinese; SayHello s2 = SayEgnlish; Console.WriteLine(s1("好好好")); Console.WriteLine(s2("Gooood")); // 最推荐 SayHello ss1 = con => con; Console.WriteLine(ss1("niiiice")); // 匿名委托:一次性委托 SayHello ss3 = delegate(string s) { return s; }; Console.WriteLine(ss3("说中国话")); } 常规实例化委托 SayHello sayC = new SayHello(SayChinese);:创建了一个 SayHello 委托的实例 sayC,并将 SayChinese 方法作为参数传递给委托的构造函数。 Console.WriteLine(sayC("欢迎大家"));:通过委托实例调用 SayChinese 方法,并输出结果。 同理,SayHello sayE = new SayHello(SayEgnlish); 和 Console.WriteLine(sayE("Welcome to")); 是对 SayEgnlish 方法的委托调用。 简化的委托赋值方式 SayHello s1 = SayChinese; 和 SayHello s2 = SayEgnlish;:当委托类型和方法签名一致时,可以直接将方法赋值给委托变量,无需使用 new 关键字。 Console.WriteLine(s1("好好好")); 和 Console.WriteLine(s2("Gooood"));:通过委托实例调用相应的方法。 使用 Lambda 表达式实例化委托 SayHello ss1 = con => con;:使用 Lambda 表达式创建委托实例 ss1,con => con 表示接受一个参数 con 并返回该参数本身。 Console.WriteLine(ss1("niiiice"));:通过委托实例调用 Lambda 表达式。 匿名委托 SayHello ss3 = delegate(string s) { return s; };:使用匿名委托创建委托实例 ss3,delegate(string s) { return s; } 是一个匿名方法,直接在委托实例化时定义了方法体。 Console.WriteLine(ss3("说中国话"));:通过委托实例调用匿名方法。 委托引用的方法定义 public static string SayChinese(string content) { return content; } public static string SayEgnlish(string content) { return content; } public static string SayChinese(string content) 和 public static string SayEgnlish(string content):定义了两个静态方法,分别接受一个 string 类型的参数 content,并返回该参数本身。这两个方法的签名与 SayHello 委托一致,可以被 SayHello 委托引用。 常规的委托实例化、简化的赋值方式、Lambda 表达式和匿名委托。委托在 C# 中是一种强大的机制,它允许将方法作为参数传递,实现了代码的灵活性和可扩展性。
- mybatis动态sql中的trim标签的使用(转)
- Golang 中的 Makefile 原理及使用方法
- Linux中变量#,#,@,0,0,1,2,2,*,$$,$?的含义【转】
- python中的全局变量和局部变量(转)
- 【转帖】【详细】Notepad++使用心得和特色功能介绍 -> notepad/ultraedit的最好的替代品...