Makefile入门(介绍、规则、语法、函数、实例) 您所在的位置:网站首页 makefile中clean的old警告 Makefile入门(介绍、规则、语法、函数、实例)

Makefile入门(介绍、规则、语法、函数、实例)

2024-02-26 16:44| 来源: 网络整理| 查看: 265

Makefile的基本使用

文章目录 Makefile的基本使用Makefile的介绍和规则程序的编译和链接过程Makefile的介绍What:什么是Makefile?Why:为什么使用Makefile? Makefile的规则How:如何使用Makefile? Makefile的基础语法基础语法之通配符基础语法之伪目标: .PHONY基础语法之变量 Makefile的函数Why:为什么我们要使用函数?What&&How:Makefile自带的函数有什么以及如何使用?**字符串替换与分析函数****文件名称处理函数****make控制函数****其他函数** Makefile的实例头文件依赖(生成*.d依赖文件及与-M相关参数介绍)1. 为什么要使用自动依赖文件?2、让编译器自动生产依赖关系的相关参数 添加CFLAGSWhat:什么是CFLAGS?How:如何使用CFLAGS? 参考资料

Makefile的介绍和规则

问:之前使用单片机开发的时候,使用Keil工具程序点击一下鼠标就可以进行编译了,那么它为什么可以这样如此轻松进行编译?如何组织管理大量的工程文件?如何确定编译哪一个文件?

答:实际上windows工具管理程序的内部机制,也是Makefile。当我们在Linux下来开发裸板程序的时候,使用Makefile组织管理这些程序,这些文件。

程序的编译和链接过程

一个C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)等4步才能变成可执行文件。 在这里插入图片描述

一般来说,我们会把前三个步骤即预处理、编译和汇编统称为编译。

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件(.h 文件)中应该只是声明,而定义应该放在C/C++文件(.c 文件)中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。

链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File)。在大多数的时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“*库文件”(Library File)*,也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

总结一下,源文件首先会生成中间目标文件(O文件或是OBJ文件),再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error)。

Makefile的介绍 What:什么是Makefile?

​ 在 Linux(unix )环境下使用GNU 的make工具能够比较容易的构建一个属于你自己的工程,整个工程的编译只需要一个命令就可以完成编译、连接以至于最后的执行。不过这需要我们投入一些时间去完成一个或者多个称之为Makefile 文件的编写。

​ 所要完成的Makefile 文件描述了整个工程的编译、连接等规则。其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建哪些库文件以及如何创建这些库文件、如何最后产生我们想要的可执行文件。

​ make是一个命令工具,它解释Makefile 中的指令。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile 有自己的书写格式、关键字、函数。像C 语言有自己的格式、关键字和函数一样。

​ make命令执行的时候,需要一个Makefile文件(此文件不需要任何后缀),这个Makefile文件就告诉了make命令如何去编译和链接程序。后续就能够使用一个make命令就能快速自动对某些文件进行重编译和链接目标程序。而要想快速编译和链接,就需要写好Makefile文件。

​ 注意:Makefile文件有以下的书写规则:

​ 1.如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。

​ 2.如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。

​ 3.如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

Why:为什么使用Makefile?

​ 编写一个Makefile文件尽管看起来可能是很复杂的事情,但是为工程编写Makefile 的好处是能够使用一行命令来完成“自动化编译”,一旦提供一个(通常对于一个工程来说会是多个)正确的 Makefile。编译整个工程你所要做的事就是在shell 提示符下输入make命令。整个工程完全自动编译,极大提高了效率。

Makefile的规则

Makefile最基本的语法就是规则,规则如下:

目标 : 依赖1 依赖2 ... [TAB]命令

当“依赖”比“目标”新,执行它们下面的命令。这就是Makefile最基本的语法规则。

下面,将写一个程序来实验一下:

文件a.c

02 #include 03 04 int main() 05 { 06 func_b(); 07 return 0; 08}

文件b.c

2 #include 3 4 void func_b() 5 { 6 printf("This is B\n"); 7 }

编译:

gcc -o test a.c b.c

运行:

./test

结果:

It is B

gcc -o test a.c b.c 这条命令虽然简单,但是它完成的功能不简单。

(提醒:gcc -o test a.c b.c -v :加上一个**‘-v’**选项可以看到它们的处理过程,包括编译和链接的过程,include的路径等等信息。)

从上文的程序的编译和链接就可以知道 .c 程序—> 可执行程序要按顺序经过四个步骤:预处理、编译、汇编、;链接。对gcc -o test a.c b.c 命令进行具体分析,可知:

①对于a.c:执行:预处理 编译 汇编 的过程,a.c ==>xxx.s ==>xxx.o 文件。

②对于b.c:执行:预处理 编译 汇编 的过程,b.c ==>yyy.s ==>yyy.o 文件。

③最后:xxx.o和yyy.o链接在一起得到一个test应用程序。

第一次编译 a.c 得到 xxx.o 文件,这是很合乎情理的。

执行完第一次之后,如果修改 a.c 又再次执行:gcc -o test a.c b.c,这一条命令,对于 a.c 应该重新生成 xxx.o,但是对于 b.c 又会重新编译一次,这完全没有必要,因为b.c 根本没有修改,直接使用第一次生成的 yyy.o 文件就可以了。

缺点:使用gcc -o test a.c b.c 则会对所有的文件都会再处理一次,即使 b.c 没有经过修改,b.c 也会重新编译一次,当文件较少时,这还没有什么问题。当文件非常多的时候,就会导致效率太低,编译时间过长。如果文件非常多的时候,我们,若仅仅只修改了少量的文件,但所有的文件都会重新处理一次,编译的时候就会等待很长时间。

解决方法:对于这些源文件,我们应该分别处理,执行:预处理 编译 汇编,先分别编译它们,最后再把它们链接在一起,比如:

编译:

gcc -o a.o a.c gcc -o b.o b.c

链接:

gcc -o test a.o b.o

比如:上面的例子,当我们修改a.c之后,a.c会重现编译,得到新的a.o 文件,然后再把新的a.o 和 旧的 b.o 文件链接在一起就可以了。而b.c就不需要重新编译,节省时间了。

那么问题又来了,怎么知道哪些文件被更新了/被修改了?

比较时间,比较 a.o 和 a.c 的时间,如果a.c的时间比 a.o 的时间更加新的话,就表明 a.c 被修改了,同理b.o和b.c也会进行同样的比较。比较test和 a.o,b.o 的时间,如果a.o或者b.o的时间比test更加新的话,就表明应该重新生成test。而由Makefile的基本书写规则得知,它就是这样做的。

How:如何使用Makefile?

写一个基本的Makefile:

根据Makefile的基本规则:

目标 : 依赖1 依赖2 ... [TAB]命令

当“依赖”比“目标”新,就执行它们下面的命令。我们要把上面三个命令写成makefile规则,如下:

test :a.o b.o #test是目标,它依赖于a.o b.o文件,一旦a.o或者b.o比 #test新的时候,就需要执行下面的命令,重新生成test可执行程序。 gcc -o test a.o b.o a.o : a.c #a.o依赖于a.c,当a.c更加新的话,执行下面的命令来生成a.o gcc -c -o a.o a.c b.o : b.c #b.o依赖于b.c,当b.c更加新的话,执行下面的命令,来生成b.o gcc -c -o b.o b.c

我们来作一下实验:

在a.c 文件和b.c 文件所在的目录下,建立一个Makefile文件(注意不添加任何后缀):

文件:Makefile

1 test:a.o b.o 2 gcc -o test a.o b.o 3 4 a.o : a.c 5 gcc -c -o a.o a.c 6 7 b.o : b.c 8 gcc -c -o b.o b.c

上面是Makefile中的三条规则。Makefile,就是名字为“Makefile”的文件。

当我们想编译程序时,直接执行make命令就可以了。

一执行make命令它想生成第一个目标test可执行程序。如果发现a.o 或者b.o没有,就要先生成a.o或者b.o,发现a.o依赖a.c,有a.c但是没有a.o,他就会认为a.c比a.o新,就会执行它们下面的命令来生成a.o,同理b.o和b.c的处理关系也是这样的。

在这里插入图片描述

​ 我们第一次执行make的时候,所有命令都执行,由Makefile第一条书写规则可知(如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接)

在这里插入图片描述

​ 我们再次执行make就会判断Makefile文件中的依赖,发现依赖没有更新,所以目标文件就不会重现生成,就会有上面的提示“make: `test’ is up to date.”

​ 若只是修改了a.c 文件,a.c文件就比a.o文件更加新,所以就会执行gcc -c -o a.o a.c 来更新a.o文件。而a.o比test新,所以又要使用gcc -o test a.o b.o 来重新链接生成test可执行程序。

在这里插入图片描述

Makefile的核心就是其里面的规则!!!

执行make命令的时候,就会在当前目录下面找到名字为:Makefile的文件,根据里面的内容来执行里面的判断/命令。

Makefile的基础语法

​ 在这里仅仅只是展示最基础语法,若想深入了解Makefile,

可学习官方文档: http://www.gnu.org/software/make/manual/

或查阅书籍:GNU Make 使用手册(中译版)https://file.elecfans.com/web1/M00/7D/E7/o4YBAFwQthSADYCWAAT9Q1w_4U0711.pdf

基础语法之通配符

what:什么是通配符?

​ 通配符是一种特殊语句,主要有星号(*)和问号(?),用来模糊搜索文件。当查找文件夹时,可以使用它来代替一个或多个真正字符;当不知道真正字符或者懒得输入完整名字时,常常使用通配符代替一个或多个真正的字符。

why:为什么要使用通配符?

​ 假如一个目标文件所依赖的依赖文件很多,那样岂不是我们要写很多规则,这显然是不合乎常理的,而且十分麻烦,所以我们可以使用通配符,来解决这些问题。

how:如何使用通配符?

基本的通配符:

%.o:表示所用的.o文件

%.c:表示所有的.c文件

$@:表示目标文件

$ printf("This is C = %d\n", C); }

c.h:

#define C 10086

那么有如下的依赖关系:

c.o:c.c stdio.h c.h

如果在使用Makefile的时,编写目标文件的依赖关系没有加上c.h文件,当c.h中的内容改变的时候,根本不会重新编译新的c.o文件,这会发生十分致命的错误,因为目标文件c.o内部引用了c.h文件中的宏定义。

若是大型的工程,我们必须清楚每个源文件中包含了哪些头文件,一旦增加或删除某些头文件,又要修改相应的Makefile,这就是很繁琐且很容易出错的工作。

因此,为了避免出现以上问题,我们需要做出改变,让它自动获取源文件中包含的头文件,并生成一个依赖关系,此时就使用后缀名为.d 的依赖文件。

这样做的优点就是:

不必手动书写若干目标文件的依赖关系,由编译器自动生成不管是源文件还是头文件有更新,目标文件都会重新编译 2、让编译器自动生产依赖关系的相关参数

部分参数如下:

-M-MF-MD

-M

生成文件的依赖关系,同时也把一些标准库的头文件包含了进来。其本质是告诉预处理器输出一个适合 make 的规则,用于描述各目标文件的依赖关系。

举例:

gcc -M c.c #打印出依赖

则在终端中就会输出:

在这里插入图片描述

这个参数同时可以查看某个源文件所包含的所有头文件。可以检查自己希望程序包含的头文件有没有包含进去。

-MF File

当同时使用了 “-M”选项时,则把依赖关系写入名为 “File” 的文件中。若同时也使用了 “-MD” 或 “-MMD”,“-MF” 将覆写输出的依赖文件的名称 。

举例:

gcc -M -MF c.d c.c #把依赖写入文件c.d

结果:

“-M”输出的内容就保存在c.d文件中。可使用“cat c.d”命令查看依赖。

在这里插入图片描述

-MD

等同于 -M -MF File,但是默认关闭了 -E 选项。其输出的文件名是基于 -o 选项,若给定了 -o 选项,则输出的文件名是 -o 指定的文件名,并添加 .d 后缀,若没有给定,则输入的文件名作为输出的文件名,并添加 .d 后缀,同时继续指定的编译工作。

举例:

gcc -c -o tmp.o -MD main.c

本目录下生成了以下文件: tmp.d tmp.o

另一个例子:

gcc -c -o c.o c.c -MD -MF c.d #可以编译生成c.o, 并生成依赖写入文件c.d中

利用上文所说的参数,修改原本的Makefile。

修改Makefile如下:

objs = a.o b.o c.o dep_files := $(patsubst %,.%.d, $(objs)) dep_files := $(wildcard $(dep_files)) test: $(objs) gcc -o test $^ ifneq ($(dep_files),) include $(dep_files) endif %.o : %.c gcc -c -o $@ $


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有