5.Makefile简单使用实例

目录

1.什么是Makefile?

2.Makefile引入

3.Makefile的基本规则

4.Makefile实例

5.Makefile简单语法

6.Makefile 函数

     6.1.foreach 函数

     6.2.过滤函数 -filter

     6.3.获取匹配模式文件名函数— wildcard

     6.4.模式替换函数— patsubst 

7.Makefile 综合例子


   1.什么是Makefile?

makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

makefile的好处就是:

—“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具


2.Makefile引入

例子1:

新建两个c文件,分别是A.c和B.c内容如下:

上传到Linux系统使用gcc进行编译

使用命令:gcc -o out A.c B.c

解释:

虽然在在编译A.c和B.c文件,然后把它们连接在一起,只使用了一个命令,他们需要经过四个过程才能生成可执行文件out

这四个过程分别为:

预处理:预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理。

② 编译:编译就是把 C/C++代码(比如上述的“.i”文件)“翻译”成汇编代码

汇编:汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在 Linux 系统上一般表现为 ELF 目标文件(OBJ 文件)

链接:连接就是将上步生成的 OBJ 文件和系统库的 OBJ 文件、库文件连接起来,最终生成了可以在特定平台运行的可执行文件。

注意:一般把前面三个步骤统称为编译

其生成步骤的文件大致如下:

对于命令gcc -o out A.c B.c ,加入我们修改了A.c文件的内容,那么需要重新汇编生成x.o文件,虽然没有修改B.c文件,此时gcc还是会默认再生成一次y.o文件,然后把x.o和y.o文件进行链接,这并不是我们想要的(我们想要的是如果y.o没有修改就不需要去重新生成一次了,直接使用旧的y.o文件),在文件很少的情况下几乎看不出有什么缺点,但是当文件很多的时候编译的时候就会花上很长的时间。这也是gcc的一个缺点。那如何改善呢?

解决方案:

1).对于每个c文件都分开编译(通常说的编译由三部分组成:预处理、编译、汇编,使用gcc命令:gcc -c -o x.o A.c和gcc -c -o  y.o B.c),最后链接起来(使用命令:gcc -o out x.o y.o)

2).每次编译之前判断.c文件的内容是否比.o文件新,如果.c比.o新,或者.o文件不存在,那么就要重新进行编译和汇编的过程,        生成相应的.o文件。

那么上述的例子可以用下面的三条语句进行编译:

gcc -c -o x.o A.c

gcc -c -o y.o B.c

gcc -o out x.o y.o

解释:如果在第一次编译的时候,还没有生产x.o和y.o文件,所以会生成这两个文件,然后把这两个文件链接在一起生成out可执行程序,此时如果只修改了A.c的话,那我们只需要去重新生成 x.o文件即可,然后再和旧的y.o重新连接在一起生成新的out就可以,第二条语句没有必要执行。

  • 那如何判断文件是否被修改呢?

通过判断文件的修改时间

1.当A.c的修改时间比x.o的时间新的话或者x.o不存在,那么就需要执行gcc -c -o  x.o A.c 语句,生成 x.o文件,B.c同理

2.当 x.o 或 y.o 比out文件新的话或out不存在,那么就需要执行gcc -o out x.o y.o 把x.o和y.o链接重新生成out文件

  • 那我们需要在程序中去判断时间吗?

不需要的,因为这个步骤Makefile已经帮我们做好了,我们只需要遵循Makefile的语法即可


3.Makefile的基本规则

target:prerequisites
    command
    ...
    ...
=====================
目标 : 依赖文件
[tab键] 命令
      ...
      ...

target:是目标文件,可以是object file,也可以是执行文件,还可以是一个标签label。

prerequisites:就是要生成那个target所需要的文件或者目标。

command:也就是make需要执行的命令-。

  这是一个文件以来关系,也就是说target是由一个或多个目标文件依赖于prerequisites中的文件,其生成规则定义在command中,而且只要prerequisites中有一个以上的文件比target文件更新的话,command所定义的命令就会被执行,这是makefile的最基本规则,也是makefile中最核心的内容。

  • 当 “依赖文件(prerequisites)”   比  “目标文件(target)”   新     =========>执行命令(command

以上面的例子来分析写Makefile

分析:

现在我们的目标是生成   out  的目标文件,他们的依赖是 x.o 和 y.o  (把它们连接在一起生成out),可以这样写:

out : x.o y.o

[tab键]  gcc -o out x.o y.o

那么x.o 呢? 它是由A.c生成的,所以x.o的依赖是 A.c.可以这样写:

x.o : A.c
[tab键] gcc -c -o x.o A.c

那么y.o 呢? 它是由B.c生成的,所以y.o的依赖是 B.c.可以这样写

y.o : B.c
[tab键] gcc -c -o y.o B.c

在Linux系统终端中,直接输入make命令就可以实现自动编译了,输入make命令,执行结果大致如下描述:

1.首先生成out目标文件,这个文件依赖于x.o和y.o,发现没有x.o那么向下寻找

2.找到 x.o 这个文件依赖于 A.c ,而x.o这个文件还不存在,系统认为A.c比x.o新,执行gcc -c -o x.o A.c 命令,生成x.o,

   生成b.o的过程同理。

3.生成了a.o和b.o了之后,test的依赖文件都有了,而且判定比out新,所以使用命令:gcc -o out x.o y.o,生成out的目标文件。

4.假设现在修改了A.c,再次执行make命令,我们的目的是想要生成第一个目标out,发现x.o和y.o其中有一个比out新,那么就需要     重新执行下面的命令,可知是a.o比out新,那么找到生成a.o目标文件的依赖,然后执行gcc -c -o x.o A.c,重新生成x.o文件。       而B.c文件并没有比y.o文件新,所以不执行命令,最终把新生成的x.o和旧的y.o重新连接生成out目标文件。


4.Makefile实例

例子1:

在上面例子的基础增加一个Makefile:

新建一个名为 Makefile 的文件:

上传到Linux系统进行编译:

现在修改一下A.c文件,然后再尝试make一下:

重新上传A.c到Linux系统,编译

如果再次执行一次make是什么反应呢?

提示 out 这个目标文件已经是最新的了,检测到没有修改,所以不需要重新生成,这样就可大大提高我们工作的效率,智能检测我们的文件是否被修改,哪个文件需要重新生成。


5.Makefile简单语法

5.1.通配符

重新看一看上边所写的Makefile文件:

当文件很少的时候,可以这样写,但是当你需要编译的文件很大呢,一万个?你需要写一万个这样的语句吗吗?

这个时候我们就可以使用通配符

  • 通配符符号:%

①: $@   表示目标文件

②: $<     表示第1个依赖文件

③: $^     表示所有依赖文件

例子:把上面的Makefile使用通配符的方法表示:

上传到Linux系统,然后make:


5.2.假想目标

在上面例子的基础加上一个clean的目标文件,当调用make clean 的时候,清除所有.o文件和out文件

修改如下:

上传到Linux系统编译:

提示:通过上面的规律可以,make命令是可以带上目标名的,如果make后面不跟目标名字的话,默认生成第一个目标,当带上目标名的话,生成指定的目标

现在使用make clean 可以正常执行,但是如果在这个目录下,有名为clean的文件会怎么样?

实验一下:

发现,使用make clean 命令,已经不会生成clean目标了。

为什么?

我们看一看Makefile的核心规则:

1.当目标文件不存在

2.某个依赖文件比目标文件新

但是现在目录中有名为clean的文件,那目标文件存在,那就取决于依赖,但是在Makefile中clean目标没有依赖,所以没有办法通过判断依赖的的时间去更新clean目标,所以clean目标文件一直都是目录中那么clean文件。

所以说,如果目录中有何clean同名文件时就没有办法执行clean操作了。

解决方法:把这个目标定义为假想目标

使用关键字定义假想目标: .phony

修改如下:

上传编译:


5.3.变量

变量的分类:

1.既时变量(简单变量):

例子: 

赋值方式: A := xxx    #A的值在定义就可以确定,即刻赋值

2.延时变量

例子:

赋值方式:A =xxx      #A的值在使用到的时候才会确定

赋值方式:

:=     即时变量

       延时变量

?=      延时变量,第一次定义才有效,如果这个变量在前面已经定义过,那么不执行这句赋值

+=       可以是即时变量也可以是延时变量,取决于这个变量的定义

实例:

建立一个Makefile文件,建立两个变量,然后赋值打印

编译:


这样看不出即时变量和延时变量的区别,修改一下Makefile,如下:

编译:

可以做如下修改,运行结果也是一样的,因为当你执行make的时候,是把整个Makefile文件读取进去分析的。


再修改Makefile.如下:

编译,执行

其实B的值就是等于C最终赋值的那个数值,再次修改一下:

编译:

也可以使用  += 进行附加的赋值,修改如下:

编译,运行:


修改Makefile, 关于(?=)的使用:

编译:

再次修改一下:

编译:

通过命令行给D赋值,覆盖原来D的值:


6.Makefile 函数

6.1.foreach 函数

函数“foreach ”不同于其它函数。它是一个循环函数。类似于 Linux 的 shell 中的for 语句

  • 语法$(foreach VAR,LIST,TEXT)
  • 函数功能: 这个函数的工作过程是这样的:如果需要(存在变量或者函数的引用),首先展开变量“ VAR”和“ LIST”的引用;而表达式“ TEXT”中的变量引用不展开。执行时把“ LIST”中使用空格分割的单词依次取出赋值给变量“VAR”,然后执行“ TEXT”表达式。重复直到“ LIST”的最后一个单词(为空时结束)。“TEXT ”中的变量或者函数引用在执行时才被展开,因此如果在“TEXT”中存在对“ VAR”的引用,那么“ VAR”的值在每一次展开式将会到的不同的值。
  • 返回值: 空格分割的多次表达式“ TEXT ”的计算的结果。

例子:

编译:


6.2.过滤函数 -filter

“filter”函数可以用来去除一个变量中的某些字符串, 我们下边的例子中就是用到了此函数。

1).找出符合PATTERN 格式的值

  • 语法$(filter PATTERN ⋯,TEXT)
  • 函数功能:过滤掉字串“ TEXT”中所有不符合模式“ PATTERN ”的单词,保留所有符合此模式的单词。可以使用多个模式。模式中一般需要包含模式字符“%”。存在多个模式时,模式表达式之间使用空格分割。
  • 返回值:空格分割的“ TEXT”字串中所有符合模式“ PATTERN ”的字串。

2).找出不符合PATTERN 格式的值

  • 语法$(filter-out PATTERN ⋯,TEXT)
  • 函数功能:过滤掉字串“ TEXT”中所有符合模式“ PATTERN ”的单词,保留所有不符合此模式的单词。可以使用多个模式。模式中一般需要包含模式字符“%”。存在多个模式时,模式表达式之间使用空格分割。
  • 返回值:空格分割的“ TEXT”字串中所有不符合模式“ PATTERN ”的字串。

例子:

编译:


6.3.获取匹配模式文件名函数— wildcard

PATTERN”使用 shell可识别的通配符,包括“ ?”(单字符)、“*”(多字符)等

  • 语法$(wildcard PATTERN)
  • 函数功能:列出当前目录下所有符合模式“ PATTERN”格式的文件名。
  • 返回值:空格分割的、存在当前目录下的所有符合模式“ PATTERN”的文件名。

例子:

创建一个makefile,然后添加几个c文件和h文件

编写Makefile文件:

编译

结论:这个函数的功能是,以我们想要的文件格式寻找当前目录下存在的文件。

例子2:

寻找真实存在的文件,代码如下:

编译:


6.4.模式替换函数— patsubst 

参数 “TEXT ”单词之间的多个空格在处理时被合并为一个空格,并忽略前导和结尾空格。

  • 语法:$(patsubst PATTERN,REPLACEMENT,TEXT)
  • 函数功能:搜索“ TEXT”中以空格分开的单词,将否符合模式“ TATTERN ”替换为“REPLACEMENT ”。参数“PATTERN”中可以使用模式通配符 “%”来代表一个单词中的若干字符。 如果参数“REPLACEMENT ”中也包含一个“%”,那么“ REPLACEMENT ”中的“ %”将是“ TATTERN”中的那个“ %”所代表的字符串。在“ TATTERN ”和“REPLACEMENT ”中,只有第一个“ %”被作为模式字符来处理,之后出现的不再作模式字符(作为一个字符)。在参数中如果需要将第一个出现的“ %”作为字符本身而不作为模式字符时,可使用反斜杠“ ”进行转义处理(转义处理的机制和使用静态模式的转义一致,
  • 返回值:替换后的新字符串。

例子:

编译:


7.Makefile 综合例子

  •  gcc 的参数
  • -M参数:

        C/C++ 编译器都支持一个 “-M” 的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。

  • -MF File 
    使用-MF 把这些依赖写入File文件汇总, 将覆写输出的依赖文件的名称
  • -MD

     使用-MD参数,一个文件的所有依赖写入指定文件,同时编译这个文件

例子:

在上边的5.2节中的例子:

A.c文件和B.c文件,内容如下:

Makefile文件内容如下:


此时我们添加一个B.h的文件,然后B.h中定义一个宏,接着在B.c中调用,编译一下查看结果:

1).添加一个 B.h文件,内容如下:

2).修改B.c函数,调用val的值,打印输出,如下:

上传到Linux系统,编译运行: 

看起来好像没有什么问题, 试着修改一下B.h文件中val的值为20,试一下:

重新make一下:

此时虽然改变了B.h中的内容,但是系统检测不到,所以out的文件没有办法重新生成。当然此时可以make clean一下,然后再make就能重新生成新的out文件了,但是这不是最终的解决办法。

那么此时我们就需要去修改makefile文件了:

其实没有办法生成新的out文件,是因为检测不到B.o文件已经更新,而B.o文件现在依赖于两个文件了,就是B.c和B.h

可以看看在Makefile中,B.o的依赖只有B.c这个文件,所以可以在Makefile中加入这样一句:

上传Makefile重新编译一次,可以检测B.h文件修改,并重新生成B.o文件了


那么现在问题来了,我们此时B.c的头文件依赖只有一个,而且还是我们清楚知道的。但是文件有成千上万个,而且依赖也有很多个,那此时怎么办,一个个查找是不可能的,而且漏掉一个可能导致编译出我们不想要的结果,怎么办?

还有gcc 编译器有一个  -M 选项,可以去查找一个文件的所有依赖:

例如:查看B.c文件的所有依赖

使用命令:gcc -M B.c

可以看出:B.o依赖于 B.c 和一堆库函数文件,最后还有一个B.h

既然我们可以通过一个命令检测到一个c文件所需要的依赖,那么我们就可以利用这个功能去简化Makefile,让Makefie变得灵活起来。


 -MF File 

功能:把依赖写入一个文件
当使用了 ‘-M’ 或者 ‘-MM’ 选项时,则把依赖关系写入名为 ‘File’ 的文件中。若同时也使用了 ‘-MD’ 或 ‘-MMD’,’-MF’ 将覆写输出的依赖文件的名称

例子:

把B.c的所有依赖写入B.d中

使用命令:gcc -M -MF B.d B.c

可以看到和使用命令: gcc -M B.c的结果是一样的


  -MD 

例子:

一个.c的所有依赖写入文件,同时编译这个文件

使用命令: gcc -c -o  B.o B.c -MD -MF B.d

这样一来,这条命令就能把 B.c的依赖写入文件B.d中,又能编译B.c文件生成B.o文件


 现在可以优化上面的Makefile文件了,修改如下:

上传到Linux系统,编译:

可以看到不仅编译了A.c和B.c文件,还同时把依赖文件写入A.o.d和B.o.d文件

既然我们可以把依赖文件写入一个文件,那我们可以把这些依赖文件给包含进来,这样就省去了下面这一句:

改写为:

此时我们就需要判断是否有 .d 的文件,如果有才包含进入,如果没有则不包含:

1).首先使用一个变量,将需要连接的文件放在一起:

val = A.o B.o

2).那么他们相应的依赖文件就全部需要给他们就上后缀 .d,用到一个前面的函数:模式替换函数— patsubst 

把val中所有的文件加上前缀(.)和后缀 (.d)

dep_files := $(patsubst  %,.%.d,$(val))

3).使用 wildcard函数判断dep_files中文件是否真是存在

dep_files :=$(wildcard,$(dep_files ))

4).使用ifneq判断是否有依赖文件,如果变量dep_files 不等于空,说明有依赖文件,就使用include把依赖文件包含进入

ifneq($(dep_files ),)

#include $(dep_files)

endif

整体的代码如下:

val = A.o B.o

#给val中所有的文件加上前缀和后缀,结果如下:
# dep_files = .A.o.d  .B.o.d
dep_files :=$(patsubst %,.%.d,$(val))
#检测 dep_files 是否真实存在,只提取存在的文件
dep_files :=$(wildcard $(dep_files))
#生成目标文件out也是默认生成的文件
out :$(val)
	gcc -o out $^
#如果dep_files不为空,说明有依赖文件,包含进来
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o:%.c
	gcc -c -o $@ $< -MD -MF [email protected] 
clean:
	rm *.o out *.d
#删除依赖
disclean:
	rm $(dep_files)
.PHONY:clean

这样一来,就这个Makefile就灵活多了,即时是增加.c或者.h文件,只需要稍微修改一下Makefile就可以了。

试着make一下:

修改B.h中val的值,然后再次make一下:

在上面的基础上,做个实验,增加一个C.c文件,然后增加C.h和D.h,接着让C.c调用D.h和C.h,最后主函数再调用C.c里边的函数:

看一下各个文件的内容:

现在多了一个c文件,所以最终会多出 C.o文件,最终我们想要的结果是生成一个可执行文件out,而out的依赖文件有三个

那就是A.o 和 B.o 以及 C.o

 

那最终的Makefile值改动了一个地方,如下:

val = A.o B.o C.o

#给val中所有的文件加上前缀和后缀,结果如下:
# dep_files = .A.o.d  .B.o.d
dep_files :=$(patsubst %,.%.d,$(val))
#检测 dep_files 是否真实存在,只提取存在的文件
dep_files :=$(wildcard $(dep_files))
#生成目标文件out也是默认生成的文件
out :$(val)
	gcc -o out $^
#如果dep_files不为空,说明有依赖文件,包含进来
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o:%.c
	gcc -c -o $@ $< -MD -MF [email protected] 
clean:
	rm *.o out 
#删除依赖
disclean:
	rm $(dep_files)
.PHONY:clean

现在把这些上传到Linux系统,编译一下:

 


  • CFLAG --编译参数

1).-Werror 选项,代表是把所有的警告都当作错误来处理。

2).-I 指定头文件搜索路径。我们一般都是创建一个include 文件夹,然后把头文件全部放进一个文件夹,然后再Makefile中指定         默认头文件搜索路径。

提示:

#include < > 和 #include " " 的区别:

#include " "   表示在当前路径搜索头文件

#include < >  引用的是编译器的类库路径里面的头文件,一般是指定的路径

现在新建一个include文件夹,然后把所有的头文件放进去编译试一下:

  

创建include目录:

Makefile文件内容如下:

val = A.o B.o C.o

CFLAG = -Werror

#给val中所有的文件加上前缀和后缀,结果如下:
# dep_files = .A.o.d  .B.o.d
dep_files :=$(patsubst %,.%.d,$(val))
#检测 dep_files 是否真实存在,只提取存在的文件
dep_files :=$(wildcard $(dep_files))
#生成目标文件out也是默认生成的文件
out :$(val)
	gcc -o out $^
#如果dep_files不为空,说明有依赖文件,包含进来
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o:%.c
	gcc $(CFLAG) -c -o $@ $< -MD -MF [email protected] 
clean:
	rm *.o out 
#删除依赖
disclean:
	rm $(dep_files)
.PHONY:clean

现在Makefile中先不加 -I 参数指定头文件搜索路径,试着上传编译一下:

现在使用参数  I 指定gcc的头文件默认搜索路径:

上传Makefile再次编译一下:


发布了91 篇原创文章 · 获赞 247 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_36243942/article/details/85161179
今日推荐