0
点赞
收藏
分享

微信扫一扫

makefile简易教程


文章目录

  • 1.什么是makefile
  • 2.为什么说makefile正好适用于编译工程呢?
  • 3.makfile操练

1.什么是makefile

  • makefile语法很简单,核心思想就是执行对应的指令而已

A : 
    cmd a
B : C D
    cmd b
C :
    cmd c
D : 
    cmd d
    
#注意cmd前面必须要有tab键

你可以把A,B看作是一个指令标签,也可以用于make指令的参数,如make A 就会执行对应的指令cmd a。
cmd只是一条linux shell命令而已,可以执行任何合法的命令,如gcc, rm等。

  • B后面还跟着一个C和D,他们是干嘛的呢?
    可以把C和D看作B命令的子命令,简单来讲也就是说执行B的时候先执行C标签的指令,再执行D标签指令。
    例如上面的指令,如果执行make B,最终的执行过程是cmd c, cmd d, cmd b。
    但是执行B命令的时候不一定会执行C和D命令,这个下面要讲到。由于make程序主要用于编译项目工程,还是举个编译程序的例子:

P : test.o
	gcc -o test test.o
	
test.o : test.c
	gcc -c test.c

(1)执行上面的make,我们发现第一次执行时,两条指令都被执行了。可是再次执行时,只有第一条指令被执行。这是为什么呢?
为了讲明白这个,我们引入两个概念,目标和依赖。
即命令标签为目标,子命令为依赖。
上面的makefile中目标P依赖test.o,目标test.o依赖test.c。

(2)makefile命令被执行时有这样的一个规则:如果目标比依赖新,就不用执行该目标对应的命令,当然如果目标文件不存在的话
肯定是要执行对应的命令的。

(3)当执行make时,如果没有参数,默认是执行第一个指令,即P。
由于目标P依赖test.o,他就先去执行test.o命令。刚开始的时候是没有test.o的,于是执行test.o对应的指令gcc -c test.c,
生成了test.o。
第二次执行test.o时,由于test.o已经生成,且生成时间比test.c要晚,所以不用再次执行对应的命令。

(4)我们看到这种规则是正好适用于编译工程的。当目标文件生成后,再次执行目标命令时是不会执行对应的命令的,这样有利于
节省编译的时间。
只有当依赖文件变动后,目标文件变的比依赖文件早,这时就需要且必须重新执行对应的命令了。

(5)当我们去掉依赖文件时,例如上面的test.o后面没有test.c,虽然仍然可以执行下面对应的命令,生成test.o。
如果已经生成了test.o,当我们改动test.c文件后,再也不会重新生成test.o了,最终导致我们抓狂的找为什么改动不生效的bug。
所以我们编写makefile时一定要记得加上依赖文件。

(6)以上就是makefile文件的核心,我们发现makefile文件其实就是来执行shell 命令的,只不过他的语法规则正好适用于编译工程。
或者说为了编译工程发明了make程序及其对应的makefile脚本。

2.为什么说makefile正好适用于编译工程呢?

  • 因为一般编译一个工程正好需要两步:编译和链接,而且这两步有依赖关系,也就是说先编译生成中间文件,然后才能链接成可执行文件。
    如下所示:

Make_Execute_Progaram : Make_Obj_File
    gcc xxx.c -o target.x 
    
Make_Obj_File:
    gcc -c  xxx.c -o xxx.o

从上面的例子来看,makefile好像和shell脚本没什么差别,如果真的是这样直接用shell脚本就好了。
事实上makefile有更强大的功能,他能像编程语言一样有相应的语法和变量,在编译大型工程,特别是跨平台编译时会很高效。

  • makefile变量名的声明和引用:

src = http.c         #变量名和值之间可以有空格
target = http.o
 
A : $(src)
	gcc -c $(src) -o $(target)


$()符号就是引用变量名了,以上替换后变为:


src = http.c         #可以有空格
target = http.o
 
A : http.c
	gcc -c http.c -o http.o
	
执行make A 就会执行gcc -c http.c -o http.o,A后面的http.c是他的依赖文件,其实这里可以不写。
但是一般写上是为了检测http.c文件是否存在,更重要的是判断文件是否有更新。

3.makfile操练

  • 有这样一个需求,先将文件下的所有c文件编译,然后链接成可执行文件

(1)初级写法:笨蛋写法
app: a.o b.o c.o ....
    gcc -o app a.o b.o c.o
a.o : a.c
    gcc -c -o a.o a.c
b.o : b.c
    gcc -c -o b.o b.c
...

(2)聪明的写法:
gcc至少支持通配符,这样就可以解决问题:gcc *.c -o app。
我们先介绍makefile通配符匹配所有文件的函数--wildcard,用法如下:

查找文件夹下所有的c文件:
	src = $(wildcard *c)
	#然后我们可以这样:
	app : $(src)
	    gcc -o app $(src)

wildcard就是一个makefile函数,调用时的规则如下:
$(<function> <arguments> )
或是
${<function> <arguments>}

<function>就是函数名,make支持的函数不多。
<arguments>是函数的参数,参数间以逗号“,”分 隔,而函数名和参数之间以“空格”分隔。
函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。
感觉很像一个变量,是不是?


(3)为了更精准的把控整个项目,我们希望对每个c文件分开编译,然后链接,又该怎么办呢?
我们再介绍两个函数subst与patsubst,他们的作用都是字符串处理函数。

字符串替换函数1:
$(subst <from>,<to>,<text> )
名称:字符串替换函数——subst。
功能:把字串<text>中的<from>字符串替换成<to>。
返回:函数返回被替换过后的字符串。
eg:
x = $(subst .c,.o, a.c b.c )
将a.c b.c中的.c替换为.o,即x = a.o b.o

字符串替换函数2:
 $(patsubst <pattern>,<replacement>,<text> )
名称:模式字符串替换函数——patsubst。
功能:
查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,
如果匹配的话,则以<replacement>替换。
这里,<pattern>可以包括通配符“%”,表示任意长度的字串。
如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。
(可以用“/”来转义,以“/%”来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串。
eg:
x = $(patsubst %.c, %.o, a.c b.c )
将a.c b.c中的.c替换为.o,即x = a.o b.o
 
substr和patsubset的区别如下:subst不是那么的智能,连空格也会替换,
eg:
subst .c, .o, a.c 替换成了a. o,而patsubst则不会
subst .c,.o, a.cc 替换成了a.oc,而patsbust则不会替,保留a.cc

字符串替换函数3:
再介绍一种将变量替换成字符串的方法,eg:
x = a.c b.c 
y = $(x:.c=.cpp)   #y将变成a.cpp b.cpp注意=两边都不能有空格 
y = $(x:.c=)       #y将变成a b,注意=两边都不能有空格,一般用这个得到可执行文件

  • 但是,是否所有的.o文件都要单独写编译指令呢?
    当然也不是,这里可以用makefile的模式规则,eg:

src = $(wildcard *.c)
objs = $(patsubst %.c, %.o, $(src)) 
app : $(objs)
	gcc  $^ -o $@       #$^表示所有的依赖文件,生成最终目标文件时,肯定要依赖所有的中间文件,$@表示目标文件
	
%.o : %.c    #模式规则,也可以写成.c.o:,不是.o.c:    
	gcc -c $< -o $@     #$<表示第一个依赖文件,因为在编译的时候,我们往往只编译一个c文件,其余可能都是.h文件,他们不参与编译


为什么这里可以用模式规则呢,因为gcc在生成可执行目标文件的时候,其实隐含有一个编译为中间代码的步骤,
eg:
objs = $(patsubst %.c, %.o, $(src)) 
app : $(objs)
    gcc -o app $(objs)    #这里会先调用cc -c -o x.o x.c生成中间文件

我们可以通过模式规则自定义执行命令来替换隐含的那个过程,达到想要的效果。

参考:


https://seisman.github.io/how-to-write-makefile/introduction.html#id2


举报

相关推荐

0 条评论