什么是makefile?教你简单编写和使用Makefile

2023-05-16

什么是Makefile?

Makefile可以简单的理解成一个工程文件的编译规则。

Makefile文件描述了Linux系统下C/C++项目工程的编译规则,它的作用是用来自动化编译C/C++的项目。一旦我们编辑好Makefile文件,只需要一个make命令,整个项目工程就开始自动编译,而我们也不用手动去输入各种gcc/g++的指令。一个大型的C/C++项目中的源代码文件有成百上千个,它们按照功能、类型,模块存放在不同的目录中。Makefile文件定义了一系列的规则,指明了编译文件的先后顺序,依赖关系和是否需要重新编译等。

Makefile文件的好处

Makefile文件的好处就是带来了“自动化编译”,我们不需要再去手动的执行gcc命令去生成我们所需要的东西。一旦写好Makefile文件,只需要使用make命令,整个项目完全自动化编译,大大节省了软件项目开发的效率。

如何去制作一个Makefile文件

Makefile的命名

Makefile文件的文件名只能是Makefile或者是makefile,其他其余的名字make命令识别不了。

Makefile中的注释

Makefile文件中的注释是#号开头空格后书写注释文章的。

Makefile的规则

Makefile规则的构成

makefile的规则主要由三个部分组成,分别是所要达成的目的、依赖的关系和需要执行的命令,格式由下例所示:

targets : prerequisites
	command

或者

targets : prerequisites;	command
	command

需要⚠️注意的是:

  • targets:目标是必须要有的,可以是中间文件,也可以是可执行文件,还可以是一个标签。
  • prerequisites:是我们的依赖关系,它是生成目标所需要的文件或者是目标。可以是多个,中间用空格隔开。
  • command:是make需要执行的命令(任意的shell命令,也包括gcc指令)。可以有多条命令,一条命令占一行。如果命令太长可以用换行符 \ 去隔开。

最重要的一点便是: 命令的开始行要用tab键去隔开,而不是用空格键隔开。

编程演示:用Makefile文件去生成一个简单的小程序。

首先我写了6个文件,分别是 add.c sub.c mult.c div.c head.h main.c,这个小程序可以用我们自己定义的函数去进行加减乘除运算。
它们的代码片段分别如下:
add.c

#include <stdio.h>
#include "head.h"

int add(int a, int b)
{
    return a+b;
}

sub.c

#include <stdio.h>
#include "head.h"

int subtract(int a, int b)
{
    return a-b;
}

mult.c

#include <stdio.h>
#include "head.h"

int multiply(int a, int b)
{
    return a*b;
}

div.c

#include <stdio.h>
#include "head.h"

double divide(int a, int b)
{
    return (double)a/b;
}

head.h

#ifndef _HEAD_H
#define _HEAD_H

// 加法
int add(int a, int b);
// 减法
int subtract(int a, int b);
// 乘法
int multiply(int a, int b);
// 除法
double divide(int a, int b);

#endif

main.c

#include <stdio.h>
#include "head.h"

int main()
{
    int a = 20;
    int b = 12;
    printf("a = %d, b = %d\n", a, b);
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", subtract(a, b));
    printf("a * b = %d\n", multiply(a, b));
    printf("a / b = %f\n", divide(a, b));
    return 0;
}

接下来我们将编写一个Makefile文件,文件名就是Makefile,文件内容如下

calcApp: add.c sub.c mult.c div.c main.c
        gcc add.c sub.c mult.c div.c main.c -o calcApp

我们这个时候只需要在终端敲下make命令,就会自动编译生成calcApp文件。运行一下试试:

 ./calcApp 
a = 20, b = 12
a + b = 32
a - b = 8
a * b = 240
a / b = 1.666667

成功输出,演示结束。

Makefile的工作原理

原理一:

  • 在执行命令之前,会先去检查规则中的依赖关系是否存在
    • 如果存在,则执行命令
    • 如果不存在,则向下检查其他的规则,看有没有其他规则的目标是生成这个规则的依赖的。如果找到了,则执行该规则中的命令。

之前我们编程演示的这个例子中,命令的依赖关系是存在的,所以就符合这个原理一中依赖关系存在的这一条。如果单单看这个例子,则不好去理解这个原理一,那么我们接下来去修改这个Makefile文件,让大家能更清楚的去理解一下这个原理一:

calcApp: add.o sub.o mult.o div.o main.o
	gcc add.o sub.o mult.o div.o main.o -o calcApp
add.o: add.c
	gcc -c add.c
sub.o: sub.c
	gcc -c sub.c
mult.o: mult.c
	gcc -c mult.c
div.o: div.c
	gcc -c div.c
main.o: main.c
	gcc -c main.c

修改过后的Makefile文件的功能和未修改的makefile的功能是一样的。都是生成calcApp这个可执行程序。
注意看这个Makefile文件。要执行第一条规则中的命令。我们是不是需要这些个 .o 依赖文件?
这种情况就是依赖关系不存在的情况,我们就需要先执行规则2、3、4、5,6得到这些依赖文件后再回过头执行规则1,得到我们这个makefile的目标。

这里就衍生出Makefile中特别重要的一条规则:

  • Makefile中其他规则,都是为第一条规则去服务的。

原理二:

  • 检测更新,在执行命令的过程中会去比较目标与所依赖文件的时间
    • 如果所依赖文件的时间比目标的时间早,则代表这个目标已经是最新的了,不需要更新,也就不需要执行对应规则的命令。
    • 如果所依赖文件的时间比目标的时间晚,则代表有依赖文件进行了更新,所以我们要去重新生成目标,所以要重新执行生成目标的命令。

刚刚我们只是重新修改了一下Makefile文件,还没有去执行,我们现在去执行一下:

nowcoder@nowcoder:~/Linux/lession07$ make
gcc -c add.c
gcc -c sub.c
gcc -c mult.c
gcc -c div.c
gcc -c main.c
gcc add.o sub.o mult.o div.o main.o -o calcApp

再去执行一次make命令:

nowcoder@nowcoder:~/Linux/lession07$ make
make: “calcApp”已是最新。

这一种情况,就是所依赖的文件比目标的文件早,make命令就会告诉你这个程序已是最新。
那我们去修改其中的一个文件试试?修改一下main.c文件,随便添加点什么,换行符都行。这个时候我们再去执行下make命令试试:

nowcoder@nowcoder:~/Linux/lession07$ make
gcc -c main.c
gcc add.o sub.o mult.o div.o main.o -o calcApp
nowcoder@nowcoder:~/Linux/lession07$

我们就会发现,makefile文件将我们的可执行程序更新了,这就对应原理二的第二种情况。

修改后的Makefile的好处

这个时候我们再去对比一下,修改前的Makefile文件和修改后的Makefile文件的区别到底在哪里呢?
区别其实就在于,在上述例子中我们更新了main.c这个文件,未修改前的Makefile就会将add.c sub.c 等所有文件都重新编译一遍,再链接成我们的这个可执行程序。修改后的Makefile文件只需要重新编译一下main.c这一个文件编译一下,然后和其他原来编译好的文件一起链接进去就行了。哪种写法更好,其实一看便知。当项目工程文件很大的时候,修改后的这种写法的好处一下就凸显出来了。在编译方面可以节约出很多的时间来。

回看我们刚刚写的Makefile文件其实会发现,其实写起来相当的麻烦,为了减少不必要的编译,一下子写了n多行代码。
有没有一种方法能使我们能够既减少不必要的编译又能减少我们所熟悉的代码量呢?当然有,
那就是我们接下来要讲的Makefile中的变量。

Makefile中的变量

Makefile中的变量分为预定义变量和自定义变量,预定义变量就是系统帮你定义好的,可以直接拿过来用,自定义变量顾名思义,就是自己定义的变量。那就先讲讲预定义变量吧,方便理解。

  • 预定义变量:
    • AR:归档维护程序的名称,默认值为ar
    • CC:C 编译器的名称,默认值为cc
    • CXX:C++ 编译器的名称,默认值为g++
    • $@:目标的完整名称
    • $^ :当前规则的所有依赖文件
    • $< :当前规则的第一个依赖文件

其中 $@ $^ S< 这三个为自动变量,只能在规则的命令中使用。当我们指向make命令时,会自动将其替换成所需要的文件。

  • 自定义变量
    • 变量名=值
    • 如何使用自变量: $(变量名)

我们用下面这个例子,来更好的去阐述下自定义变量的用法:
PS:记得先删除之前操作生成的.o文件和可执行程序,方便演示

# 创建自定义变量
target=calcapp
src=add.o sub.o mult.o div.o main.o
$(target):$(src)
        cc $(src) -o $@
add.o: add.c
        gcc -c add.c
sub.o: sub.c
        gcc -c sub.c
mult.o: mult.c
        gcc -c mult.c
div.o: div.c
        gcc -c div.c
main.o: main.c
        gcc -c main.c

修改好了,make后运行一下试试。

nowcoder@nowcoder:~/Linux/lession07$ make
gcc -c add.c
gcc -c sub.c
gcc -c mult.c
gcc -c div.c
gcc -c main.c
cc add.o sub.o mult.o div.o main.o -o calcapp
nowcoder@nowcoder:~/Linux/lession07$ ./calcapp 
a = 20, b = 12
a + b = 32
a - b = 8
a * b = 240
a / b = 1.666667

运行成功。仔细看,我们的Makefile文件是不是比刚刚要简洁一点了?有人问,可我还是觉得很麻烦啊,还是要写很多条规则,有没有办法将这些规则再简化一下,还能起到不重复生成以生成文件的效果。那么我想说当然可以,接下来我要讲的模式匹配,就可以解决这个问题。

Makefile中的通配符

讲到模式匹配,就得先讲一下Makefile中用到的通配符,因为在书写规则时经常用到。

  • *号表示任意一个或多个字符
  • ?号表示任意一个字符
  • %号表示任意一个字符串

Makefile的模式匹配

由于我们的make命令有自动推导的能力,所以我们隐晦的书写一下规则时,make命令可以帮助我们自动替换或补全。下面看这个例子就知道了。

我们记得删除一下刚刚编译好的多余文件。再重新书写一下makefile:

# 创建自定义变量
target=calcapp
src=add.o sub.o mult.o div.o main.o
$(target):$(src)
        cc $(src) -o $@
# 模式匹配 用%代替一个字符串
%.o: %.c
        cc -c $< 

修改完成,make后运行一下

nowcoder@nowcoder:~/Linux/lession07$ make
cc -c add.c 
cc -c sub.c 
cc -c mult.c 
cc -c div.c 
cc -c main.c 
cc add.o sub.o mult.o div.o main.o -o calcapp
nowcoder@nowcoder:~/Linux/lession07$ ./calcapp 
a = 20, b = 12
a + b = 32
a - b = 8
a * b = 240
a / b = 1.666667

来我们再来看一下,是不是异常简洁,我们用了模式匹配了后,再不用写那么多规则了。make命令自动推导,用需要的字符串去替换了我们的通配符。瞬间我们的工作就少了一大半了。

这个时候还有人说,我们能不能再简单点再简单点!我不想写那么多啥啥啥 .o文件了,可不可以再让我懒一点!
我说可以,那我们再介绍一下Makefile的函数。

Makefile的函数

Makefile中的函数有很多,阿宋在这里就简单介绍两个我们这个小程序能用的到了两个。如果大家想要学习其他函数的话,请自行搜索,推荐去这个大佬的专栏下进一步加深对Makefile的理解与学习。
前话说完了,接下来开始这个小程序能用到的两个函数的学习。

获取匹配模式文件名函数: wildcard

函数的使用模式如下:

$(wildcard PATTERN)

这个函数的功能是获取指定目录下的制定文件列表。这个PATTERN在这里指代的就是某个目录或多个目录下对应的某种文件。不同目录直接我们可以去用空格隔开。

这个函数会返回给你匹配上的文件列表。文件与文件直接会帮你用空格隔开。

模式字符串替换函数:patsubst

函数的使用模式如下:

$(patsubst <pattern>,<replacement>,<text>)

这个函数的功能就是去text中的单词有没有符合pattern这个文件格式的,如果有,则将它替换成replacement这个格式的。

这个函数会返回给你符合replacement文件格式的文件列表,文件与文件之间用空格隔开。

现在我们介绍完了这两个函数了,我们用例子再去演示一下用法:

# 创建自定义变量
target=calcapp
src=$(wildcard ./*.c)
objs=$(patsubst %.c,%.o,$(src))
$(target):$(objs)
        cc $(objs) -o $@
# 模式匹配 用%代替一个字符串
%.o: %.c
        cc -c $<                  

我们来细细讲一下上面这个案例。
src这个变量用到了wildcard这个函数,我们在当前目录下去寻找所有的.c文件去作为这个src的值。
objs这个变量用到了patsubst这个函数,我们在src这个变量中去寻找有没有符合%.c的文件,如果有,将这些文件名全部改为%.o后作为objs的值。
至此我们的文件就修改完啦,make后运行一下看看:

nowcoder@nowcoder:~/Linux/lession07$ make
cc -c mult.c 
cc -c main.c 
cc -c add.c 
cc -c div.c 
cc -c sub.c 
cc ./mult.o ./main.o ./add.o ./div.o ./sub.o -o calcapp
nowcoder@nowcoder:~/Linux/lession07$ ./calcapp 
a = 20, b = 12
a + b = 32
a - b = 8
a * b = 240
a / b = 1.666667

同样的运行成功。有了这两个函数,我们就可以不用去写n多的.o文件啦。成功的将我们的需要写的代码量再减少了一番。

我们每次编译都会去生成很多的.o文件。有没有办法让我们去使用make命令然后再将它们清除呢?答案依旧是有,继续往下看。

Makefile的伪目标。

.PHONY: 这个目标的所有依赖被作为伪目标。伪目标是这样一个目标:当使用 make 命令行指定此目标时,这个目标所在的规则定义的命令、无论目标文件是否存在都会被无条件执行。
具体用法如下:


.PHONY: clean

clean:
        rm *.o 

我们给这个makefile文件多添加几行代码。退出文件后,执行命令 make clean,就可删除对应的.o文件

nowcoder@nowcoder:~/Linux/lession07$ make clean 
rm *.o 
nowcoder@nowcoder:~/Linux/lession07$ ls
add.c  calcapp  div.c  head.h  main.c  Makefile  Makefile1  Makefile2  Makefile3  Makefile4  mult.c  redis-5.0.10  redis-5.0.10.tar.gz  sub.c
nowcoder@nowcoder:~/Linux/lession07$ 

执行成功。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

什么是makefile?教你简单编写和使用Makefile 的相关文章

  • /usr/sbin/install 到底有什么作用?

    我正在尝试安装discount https github com Orc discount on my VPS http no de它基于Solaris 设置一些环境变量后编译效果很好 但是安装失败 https gist github co
  • bash 函数保留制表符补全

    我把函数 make color make 1 ccze A in bashrc获得彩色的 make 输出 他的作品很好 但是make用于选择目标的制表符补全功能丢失 有什么方法可以保留函数中命令的制表符完成 或者我可以做其他事情来实现制表符
  • 从 Makefile 中的 C++FLAGS 中删除一个标志?

    我有一个 Makefile 其中包含另一个设置了很多默认值的 makefile 我无法编辑包含的 makefile 并且我想更改 makefile 中 C FLAGS 的值 即使它是在包含的 makefile 中设置的 具体来说 每当 de
  • 如何不在输出中打印 makefile 中的注释

    我有一个像这样的 makefile install somecommand some explanation for next command lastcommand 发生的事情是评论 some explanation for next c
  • 在 Mac OS X 上的 Makefile 中设置 PATH(但它适用于 Linux)

    我可以在 Linux 上的 Makefile 中设置 PATH 但不能在 Mac OS X 上设置 在 OS X 中 可以设置 PATH 但不会使用 这是一个演示 在带有 bash 4 1 2 1 release 和 GNU Make 3
  • 如何调用使用 Define 创建的 GNU make 宏?

    在我的 Makefile 中调用 GREP 的两种方式有什么区别吗 我有什么理由应该使用其中之一 两者似乎产生相同的结果 define GREP word 3 shell echo define FOO 0xfff00100 endef a
  • 内核makefile中的$(call cmd,tags)这里的cmd指的是什么?

    在内核 Makefile 中我发现如下代码 ctags CTAGS CSCOPE HEADERS SOURCES ETAGS ETAGSFALGS HEADERS SOURCES call cmd ctags 另外 在哪里可以找到宏或函数
  • 在许多驱动程序文件夹中创建 build-in.o

    我正在用我的自定义驱动程序构建内核 成功构建后 我发现了许多 build in o 文件 任何人都可以详细说明这些文件是如何在这里结束的吗 我只能怀疑这些与更高级别的 makefile 有关 built in o 文件是未构建为模块的内核的
  • makefile 目标依赖项取决于目标名称

    我有以下规则 SPECIAL file1 file2 o cpp a h CC c CFLAGS lt o 我希望如果 is in SPECIAL then b h已添加到依赖项列表中 有没有办法做到这一点 而不重复规则 您可以单独分配其他
  • 如何确保目标在 makefile 中的所有其他构建规则之前运行?

    我有一个 C 项目 其中包含所有其他 C 文件所依赖的生成文件 我试图在任何其他编译开始之前强制生成并编译该文件 通常它就像将该文件首先放入all 目标 但复杂的是我的 Makefile 也是由构建系统生成的 我只能将片段附加到 Makef
  • 从哪里获取 iostream.h

    我正在尝试在 Linux 中做一些事情 但它抱怨找不到 iostream h 我需要安装什么才能获取此文件 这个标准头的正确名称是iostream没有扩展名 如果您的编译器仍然找不到它 请尝试以下操作 find usr include na
  • 是否可以使用现有的 Makefile 在 Code::Blocks 中构建项目?

    编辑 我发现项目属性中有一个选项可以设置自定义生成文件 现在项目构建良好 现在 我偶然发现了如何在单击 运行 时指定要运行的目标可执行文件 代码 块 http www codeblocks org is an IDE https en wi
  • 什么是 C 语言的高效工作流程? - Makefile + bash脚本

    我正在开发我的第一个项目 该项目将跨越多个 C 文件 对于我的前几个练习程序 我只是在中编写了我的代码main c并使用编译gcc main c o main 当我学习时 这对我有用 现在 我正在独自开展一个更大的项目 我想继续自己进行编译
  • 如何将当前目录捕获为 make 变量中的绝对路径名?

    我想在 GNUmake 文件运行期间获取当前目录并将其放入 make 变量中 执行此操作的语法是什么 像这样的东西吗 DIR PWD Um no PWD有时是在您的环境中定义的 因此由 make 继承 但通常不是 你需要 CURDIR DI
  • 控制 make 命令的输出不那么冗长,不要回显每个命令

    目前 我正在使用 Makefile 来跟踪项目的所有依赖项和编译 问题是make只是输出它正在执行的所有操作 这使得很难发现 甚至读取 更重要的信息 例如编译器警告 有没有办法控制终端上显示哪些信息 我知道有一个 s沉默的选项make 但这
  • 您如何使编译器行更短?

    通常 当我与其他人一起处理一个项目时 随着时间的推移 编译器在 Makefile 中获取的库路径和包含路径的数量会变得越来越多 此外 路径也可能会变得很长 这是一个例子 g c pipe O2 Wall W DQT BOOTSTRAPPED
  • 从 makefile 内部传递命令行参数

    我有一个 makefile 我试图从中调用一个可执行文件 该可执行文件需要 5 个参数 我如何从 makefile 传递这些参数 这样做不起作用 run exe arg1 somevalue arg2 somevalue arg3 some
  • 是否可以在 Mac OS X 上构建 FreeGLUT?

    我正在做一些关于 OpenGL 的教程 http www arcsynthesis org gltut Basics Tutorial 2001 html那个使用FreeGLUT http freeglut sourceforge net
  • 如何让 Makefile 目标被多次调用?

    在下面的简单示例中 我想做一个make dist并拥有distclean之前执行的目标distdebug and distrelease PHONY distclean dist distdebug distrelease echo in
  • Ubuntu 上对 exp 的未定义引用(包括 math.h 和与 -lm 的链接)

    我在尝试在 Ubuntu 上编译使用 exp 函数的程序时遇到一些问题 我从 gcc 收到此错误 selied Apolo Dropbox practicas UAM Neuro practica3 make gcc lm o retrop

随机推荐