makefile自动变量与隐晦规则推导

makefile的使用可以大大简化程序编译的过程,不过对于新手来说makefile的执行规则理解起来还是很让人迷糊的。其中最重要的原因是makefile中使用了大量的隐晦规则和自动变量来简化makefile的编写.本节将记录一下makefile自动变量和隐晦规则的推导过程以及makefile的执行流程。

1
2
%.o:%.c
$(CC) -c $(CFLAGS) -o $@ $<

对于上面的推导规则,makefile是怎么将%c文件编译汇编成.o文件的?下面将针对这个问题进行讲解

自动变量

makefile中的自动变量实质上是对一类变量的简写,当我们在模式规则中对这类变量处理的时候可以直接使用自动变量简化makefile代码的编写。自动变量包括如目标文件,依赖文件等。下面以实例的方式列出了一些常用的自动变量:

1
2
a: a.o b.o c.o d.o
g++ -c $(CFLAGS) -o $@ $<
  • $@: 表示模式规则中的目标文件,对于上面的模式规则,$@表示a
  • $<: 表示依赖中的第一个文件.对于上述规则,$<表示a.o
  • $^: 表示所有依赖文件的集合,对于上述规则$^表示a.o b.o c.o d.o
  • $+: 表示所有依赖文件的集合(不去重).对于上述规则$+表示a.o b.o c.o d.o
  • $%: 仅当目标是函数库文件(.a)文件时,表示规则中目标成员名。如一个目标是(test.a(a.o)),此时$%表示a.o, $@表示test.a
  • $?: 所有比目标新的依赖目标的集合
  • $*: 表示目标规则中%以及%之前的部分。如若目标文件为”src/test.o”,目标文件模式为”src/%.o”,此时$*表示”src/test”。

隐晦规则自动规则推导

使用makefile的makefile的隐晦自动规则推导功能也可以让我们的makefile的代码大大简化。使用隐晦规则,我们没必要为每一个类似的规则生成都去写类似的规则。makefile会自动推导依赖文件,并根据隐含规则推导出生成当前目标的命令。
如下面的makefile:

1
2
3
4
5
6
7
8
target: a.o b.o c.o
g++ -o $@ $^ $(LIB) $(INC) $(LINKER)
a.o: a.c
g++ -c $(CFLAGS) -o a.o a.c
b.o: b.c
g++ -c $(CFLAGS) -o b.o b.c
c.o: c.c
g++ -c $(CFLAGS) -o c.o c.c

上述规则没有使用隐晦规则,对于每一个.o文件的生成都写了一条规则语句.若使用隐晦规则推导,上述makefile可写为如下:

1
2
3
4
5
6
7
SOURCE := $(shell find ./ -type f -name *.c)
OBJECTS := $(patsubst *.c,*.o,$(SOURCE))
target : $(OBJECTS)
g++ -o $@ $^ $(LIB) $(INC) $(LINKER)
%.o:%.c
$(CC) -c $(CFLAGS) -o $@ $<

从上面的隐晦规则可以看出,对于.o文件的生成命令,makefile都可以由隐晦规则” $(CC) -c $(CFLAGS) -o $@ $<”推导出。即如当目标需要a.隐晦规则将推导出命令”g++ -c $(CFLAGS) -o a.o a.c”用于生成该目标.

makefile执行过程

由上一节中的makefile简单说一下makefile的执行规则。

1
2
3
4
5
6
7
SOURCE := $(shell find ./ -type f -name *.c)
OBJECTS := $(patsubst *.c,*.o,$(SOURCE))
target : $(OBJECTS)
g++ -o $@ $^ $(LIB) $(INC) $(LINKER)
%.o:%.c
$(CC) -c $(CFLAGS) -o $@ $<

makefile中首先声明了变量SOURCE和OBJECTS,SOURCE是当前文件夹下的所有.c文件的集合,OBJECTS是所有.c文件对应的目标文件.o的集合.

执行过程:
首先,目标target依赖所有的目标文件.o,即a.o,b.o,c.o。当需要依赖a.o时,makefile会根据隐晦规则自动推导出生成a.o文件的命令,(“g++ -c $(CFLAGS) -o a.o a.c”),生成a.o;类似的也会根据同样的过程生成b.o和c.o文件,这三个文件生成之后,再根据上述规则生成target。

makefile中常用函数

wildcard函数

函数参数:一个正则表达式
函数功能:wildcard的中文意思是通配符,它的功能类似于正则表达式,用于展开一列所有符合其参数描述的文件名,文件之间用空格分割。

实例:

1
SOURCE=$(wildcard *.cpp)

此时SOURCE的值为所有的以.cpp为后缀的文件集合,以空格隔开。

patsubst函数

函数参数: 第一个是需要匹配的样式,第二个是表示用什么替换它,第三个被处理的以空格隔开的字符串。
函数功能:其功能是一个匹配替换的函数(pattern substitute)。

实例:

1
2
#TARGET表示与SOURCE中同名的目标文件.
TARGET = $(patsubst *.cpp, *.o, $(SOURCE))

subst函数

1
$(subst <from>,<to>,<text>)

功能:字符串替换,将text中的from 替换为to
实例:

1
2
#返回值:this is b
$(subst a, b, this is a)

strip函数

1
$(strip <string>)

功能:去除头部和尾部的空格
实例:

1
2
#源字符串为"a b c ",返回值为"a b c"
$(strip a b c )

filter函数

1
$(filter <pattern...>,<text...>)

功能:过滤特定模式的字符串
实例:

1
2
#如source为a.c b.s c.h,则下列的返回值为a.c b.s
$(filter *.c *.s, $(source))

word函数

1
$(word <n>,<text>)

函数功能:取单词函数。取出中的第n个单词(从1开始),若越界,返回为空

实例:

1
2
#此函数返回值为bar
$(word 2,foo bar fun)

words函数

1
$(words <text>)

功能:统计字符串中单词的个数,返回个数
实例:

1
2
#此函数返回值为3
$(word foo bar fun)

wordlist函数

1
$(wordlist <s>,<e>,<text>)

功能:取中s-e个单词
实例:

1
2
#返回bar fun
$(wordlist 2,3, foo bar fun)

firstword函数

1
$(firstword <text>)

函数功能:取中的首个单词
实例:

1
2
#返回值为foo
$(firstword foo bar fun)

dir函数

1
$(dir ...)

功能:取给定文件名序列中的目录(即/前面的部分)。如没有/,则返回./
实例:

1
2
#返回值为/home/ ./
$(dir /home/test testfile)

notdir函数

1
$(notdir ...)

功能:取给定文件名序列中的取出非目录部分(即/后面的部分)。
实例:

1
2
#返回值为test testfile
$(notdir /home/test testfile)

suffix函数

1
$(suffix ..)

功能:取后缀函数,若没有后缀返回为空
实例:

1
2
#返回值:.c .c
$(suffix a.c b.c)

basename函数

1
$(basename ...)

功能:取前缀函数,包括目录。
实例:

1
2
#返回值:/home/test a
$(basename /home/test.cpp a.cpp)

addsuffix函数

1
$(addsuffix <suffix> <name....>)

功能:给指定文件序列添加后缀名
实例:

1
2
#返回值:a.c b.c c.c
$(addsuffix .c a b c)

addprefix

1
$(addprefix <prefix> <name...>)

功能:给指定文件序列添加前缀
实例:

1
2
#返回值:src/a src/b src/c
$(addprefix src/ a b c)

join函数

1
$(join <list1> <list2>)

功能:将两个字符串中的list对应项连接
实例:

1
2
3
4
#返回值:an 2b 3
$(join 1 2 3, a b)
#返回值:1a 2b c
$(join 1 2, a b c)

makefile中链接静态库顺序问题

在链接静态库的时候,如果多个静态库之间存在依赖关系,则有依赖的静态库之间存在顺序问题,若顺序出现错误,则可能出现函数未定义或符号找不到等错误。

静态库链接的顺序的原则是:被依赖的库一定要放在后面,因为makefile在链接静态库时的顺序是从右往左(或从后向前).如libb.a依赖于liba.a,此时的链接顺序应该是:-Llibb.a -Lliba.a。

会出现上述问题的原因是:我们在生成静态库的时候并未把依赖库的定义编到生成的库中。如

1
2
3
4
gcc -c a.c
ar cr liba.a a.o
gcc -c b.c
ar cr libb.a b.o # 虽然libb.a使用到了liba.o中的一些函数,但并不会将它们的定义包含进来,所以在链接test时需要指定这两个库