目录
前言
以下内容均来自跟我一起写Makefile,这里表示感谢,侵权删。
关于编译和链接:一般来说,无论是C还是C++,首先要把源文件编译(compile)成中间代码文件,这些中间文件在Windows下是 .obj
文件,UNIX下是 .o
文件,即Object File。然后再把所有的Object File链接(link)成执行文件。
链接时,主要是链接函数和全局变量。所以,我们可以使用这些中间目标文件( .o
文件或 .obj
文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便。所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib
文件,在UNIX下,是Archive File,也就是 .a
文件。
总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是: Link 2001错误
,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File。
-o outfile:指定生成的输出文件;
-c:仅执行编译操作,不链接;
-E:仅执行预处理;
-S:将C代码仅编译到汇编代码;
-wall:显示所有警告信息;
-w:不生成任何警告;
-I:指定include包含文件的搜索目录
-O:使用编译优化级别1编译程序。级别为1~3,级别越大优化效果越好,但编译时间越长。
书写规则
target:可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。
prerequisites:生成该target所依赖的文件和/或target
command:该target要执行的命令(任意的shell命令,一定要以Tab开头)
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说:prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。反斜杠( \
)是换行符的意思。这样比较便于makefile的阅读。在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个 Tab
键作为开头。
target ... : prerequisites ...
command
...
...
文件搜索
文件路径:下面的定义使用VAPTH变量指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
VPATH = src:../headers
另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:
vpath <pattern> <directories>
:为符合模式<pattern>的文件指定搜索目录<directories>。
vpath <pattern>
:清除符合模式<pattern>的文件的搜索目录。
vpath
:清除所有已被设置好了的文件搜索目录。
<pattern>需要包含 %
字符。 %
的意思是匹配零或若干字符,(需引用 %
,使用 \
)例如, %.h
表示所有以 .h
结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了< pattern>的文件集的搜索的目录。下面语句表示,要求make在“../headers”目录下搜索所有以 .h
结尾的文件。(如果某文件在当前目录没有找到的话)
vpath %.h ../headers
静态模式
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
...
targets:定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-pattern:指明了targets的模式,也就是的目标集模式。
prereq-patterns:目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义。
举例来说,如果<target-pattern>定义成 %.o
,意思是<target>集合中都是以 .o
结尾的,而如果<prereq-patterns>定义成 %.c
,意思是对<target-pattern>所形成的目标集进行二次定义,其计算方法是,取<target-pattern>模式中的 %
(也就是去掉了 .o
这个结尾),并为其加上 .c
这个结尾,形成的新集合。所以,“target-pattern”或是“prereq-patterns”中都应该有 %
这个字符
书写command
通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用 @
字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来向屏幕显示一些信息。如:
@echo 正在编译XXX模块......
当make执行时,会输出“正在编译XXX模块……”字串,但不会输出命令,如果没有“@”,那么,make将输出:
echo 正在编译XXX模块...... 正在编译XXX模块......
变量
变量会在使用它的地方精确地展开,就像C/C++中的宏一样。在声明时需要给予初值,而在使用时需要给在变量名前加上 $
符号,但最好用小括号 ()
或是大括号 {}
把变量给包括起来。如果你要使用真实的 $
字符,那么你需要用 $$
来表示。
两种变量定义方式:前者允许使用后定义的变量,递归时容易出错;后者使用:=操作符,禁止这种操作。
?=操作符表示:如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语句将什么也不做。
+=操作符表示给变量追加值
#------------1-----------
foo = $(bar)
bar = $(ugh)
ugh = Huh?
#------------2-----------
ugh := Huh?
bar := $(ugh)
foo := $(bar)
FOO ?= bar
如果我们要定义一个空格,那么我们可以这样来:nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,即$(nullstring)和#之间有一个空格。这样,我们可以定义出其值是一个空格的变量。这个故事告诉我们不要随意在变量的右括号和表示注释的#之间加空格。
nullstring := space := $(nullstring) # end of the line
目标变量
为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。其语法如下
<target ...> : <variable-assignment>;
<target ...> : overide <variable-assignment>
#当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
$(CC) $(CFLAGS) prog.c
foo.o : foo.c
$(CC) $(CFLAGS) foo.c
bar.o : bar.c
$(CC) $(CFLAGS) bar.c
#在这个示例中,不管全局的 $(CFLAGS) 的值是什么,
#在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则), $(CFLAGS) 的值都是 -g
条件判断
ifeq
:的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,比较参数 arg1
和 arg2
的值是否相同,表达式以圆括号括起。 else
表示条件表达式为假的情况。 endif
表示一个条件语句的结束,任何一个条件表达式都应该以 endif
结束。
ifneq:其比较参数 arg1
和 arg2
的值是否相同,如果不同,则为真。和 ifeq
类似
ifdef:ifdef <variable-name>,如果变量 <variable-name>
的值非空,那到表达式为真。否则,表达式为假。当然, <variable-name>
同样可以是一个函数的返回值。注意, ifdef
只是测试一个变量是否有值,其并不会把变量扩展到当前位置。
ifndef:ifndef <variable-name>,和ifdef
是相反的意思。
libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
函数
函数调用后,函数的返回值可以当做变量来使用。函数调用,很像变量的使用,也是以 $
来标识的,其语法如下:<function>
就是函数名; <arguments>
为函数的参数,参数间以逗号 ,
分隔,而函数名和参数之间以“空格”分隔。函数调用以 $
开头,以圆括号或花括号把函数名和参数括起。支持函数
$(<function> <arguments>)
隐含规则
编译C程序的隐含规则:<n>.o
的目标的依赖目标会自动推导为 <n>.c
,并且其生成命令是 $(CC) –c $(CPPFLAGS) $(CFLAGS)
编译C++程序的隐含规则:<n>.o
的目标的依赖目标会自动推导为 <n>.cc
或是 <n>.C
,并且其生成命令是 $(CXX) –c $(CPPFLAGS) $(CXXFLAGS)
。(建议使用 .cc
作为C++源文件的后缀,而不是 .C
)
我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如 CC
;一种是参数相的关,如 CFLAGS
。C/C++中隐含规则会用到的变量可能包括:
关于命令的变量
-
AR
: 函数库打包程序。默认命令是ar
-
CC
: C语言编译程序。默认命令是cc
-
CXX
: C++语言编译程序。默认命令是g++
-
CPP
: C程序的预处理器(输出是标准输出设备)。默认命令是$(CC) –E
-
RM
: 删除文件命令。默认命令是rm –f
关于参数的变量
下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值,那么其默认值都是空。
-
ARFLAGS
: 函数库打包程序AR命令的参数。默认值是rv
-
CFLAGS
: C语言编译器参数。 -
CXXFLAGS
: C++语言编译器参数。 -
CPPFLAGS
: C预处理器参数。( C 和 Fortran 编译器也会用到)。
模式规则
在模式规则中,target的定义需要有 %
字符,否则,就是一般的规则,表示一个或多个任意字符。在prerequisites中同样可以使用 %
,只是其取值取决于target中的 %
。例如: %.c
表示以 .c
结尾的文件名(文件名的长度至少为3),而 s.%.c
则表示以 s.
开头, .c
结尾的文件名(文件名的长度至少为5)。
#以下模式规则表示,
#如果要生成的目标是 a.o b.o ,那么 %c 就是 a.c b.c
#即target中的%是啥,prerequisites中的%就是啥
%.o : %.c ; <command ......>;
注意:%
的展开发生在变量和函数的展开之后,变量和函数的展开发生在make载入 Makefile时,而模式规则中的 %
则发生在运行时。
自动化变量
所谓自动化变量,就是这种变量会把模式规则中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完。自动化变量只应出现在规则的command中。
-
$@
: 表示规则中的target集合。在模式规则中,如果有多个目标,那么$@
就是匹配于target中模式定义的集合。 -
$<
: prerequisites的第一个目标名字。如果prerequisites是以模式(即%
)定义的,那么$<
将是符合模式的一系列的文件集。注意,其是一个一个取出来的。 -
$^
: 所有的prerequisites的集合,以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份。 -
$%
: 仅当target是函数库文件中,表示规则中的target成员名。例如,如果一个目标是foo.a(bar.o)
,那么,$%
就是bar.o
,$@
就是foo.a
。如果目标不是函数库文件(Unix下是.a
,Windows下是.lib
),那么,其值为空 -
$?
: 所有比目标新的依赖目标的集合。以空格分隔。 -
$+
: 这个变量很像$^
,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
#这个例子表示了,把所有的 .c 文件都编译成 .o 文件.
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@