什么是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 的相关文章

  • 如何在make后运行.o文件

    我一直在尝试运行一个 C 程序https github com rinon Simple Homomorphic Encryption https github com rinon Simple Homomorphic Encryption
  • 如何制作轨道时间戳

    make 如何保留文件的时间戳 我正在尝试将我的 git 存储库部署到位 我正在为大多数不会改变的文件添加预编译的二进制文件 现在 当我从 git 签出存储库时 我不想编译这些 c 文件 我想使用这些预构建的二进制文件 因此 为了设置这个方
  • 有没有比“手表制造”更明智的替代方案?

    我遇到了这个有用的提示 如果您经常处理文件并且希望它们自动构建 则可以运行 手表品牌 每隔几秒钟它就会重新运行一次 一切都会构建完成 然而 它似乎一直在吞噬所有的输出 我认为它可能更聪明 也许显示输出流 但抑制 全部 不做任何事情 这样如果
  • makefile 中的路径不起作用

    我正在运行以下命令makefile哪些需要改变dir到特定目标并在那里运行npm install 问题是我能够在输出中看到它将目录 项目 应用程序 打印到正确的目录 但安装 npm install 在上层 项目 上运行 为什么 例如 当我运
  • 即使没有任何更改,Makefile 也始终不是最新的

    我有一个包含两个文件夹的目录 src and binmakefile 位于根目录 即使没有更改 此 makefile 也会持续编译 不是最新的 我在这个 makefile 中遗漏了什么吗 all make a b a src a cpp g
  • 在 Linux 上的 makefile 和 Makefile 之间进行选择

    我想在一个目录中同时使用 Makefile 和 makefile 进行 make 默认情况下 它将执行makefile 我可以选择执行 Makefile 吗 提前致谢 最简单的选择是使用 f make f Makefile From man
  • Makefile 头依赖项

    我是使用 make 的新手 并且一直在通过以下方式学习基础知识本教程 http www cs colby edu maxwell courses tutorials maketutor 这是本教程中的最后一个 makefile 示例 IDI
  • bash:PWD 和 CURDIR 有什么区别?

    我的问题 我使用 Makefile 来运行docker runtarget 需要当前工作目录作为其参数之一 我使用任一 PWD or CURDIR build Dockerfile docker run lt PWD or CURDIR g
  • Readelf 报告程序是共享库而不是可执行文件

    使用独立的 Android NDK r10e 工具链 使用 toolchain x86 clang3 6 开关构建 出现这种奇怪的行为 交叉编译的环境变量已设置在运行makefile之前 SYSROOT指向Android工具链位置 CXX等
  • 在 Makefile 的先决条件列表中使用目标的目录路径

    我编写了一个脚本 它接收两个以 cfg 结尾的文件并输出一个以 cmp 结尾的文件 我想将其包含在我的 Makefile 中 因为一些源代码文件依赖于此 cmp 文件 在我的 Makefile 中 我想这样做 cmp cfg dir def
  • C 预处理器“/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cpp”未通过完整性检查

    在使用 Xcode 11 3 的 macOS Mojave 上 我有一个基于 Autotool 的第三方库 在终端中运行我的构建脚本时构建得很好 但在 Xcode 中运行时失败Run Script步骤为 BuildScript Showin
  • 对 sf:: 的未定义引用

    我想用 C 制作 GUI 应用程序 发现 SFML 是一个不错的选择 幸运的是 我使用的是 Linux 所以 SFML 2 4 已经安装在我的系统上 所以我开始搜索一些教程并找到了一个制作简单窗口的教程 但是当我运行代码时 出现错误 提示未
  • 从 Makefile 中的 C++FLAGS 中删除一个标志?

    我有一个 Makefile 其中包含另一个设置了很多默认值的 makefile 我无法编辑包含的 makefile 并且我想更改 makefile 中 C FLAGS 的值 即使它是在包含的 makefile 中设置的 具体来说 每当 de
  • 用于发布和调试目标的 Makefile

    我正在尝试构建一个 Makefile 它可以通过指定目标而不是变量 例如make debug 1 不太好 我这里有一个精简的简化示例 它模拟了我想要实现的目标 ifdef debug BINARY my binary debug MODUL
  • 什么是“制定目标”?

    为什么我需要制作一个make target在能够构建我的源代码之前 更具体地说 什么是制定目标 http publib boulder ibm com infocenter rsdvhelp v6r0m1 index jsp topic o
  • MinGW Make 抛出“系统找不到指定的路径。”错误

    我正在尝试在 Windows 7 上使用 cmake 生成一个 c 项目 在实际创建项目之前 cmake 会对您的工具链进行快速测试 我正在使用 MinGW 这就是我的问题所在 Cmake 触发 make 构建 最终失败并返回 系统找不到指
  • 使用 .INTERMEDIATE 在 makefile 中进行不可靠的并行构建?

    我有一个可以生成多个输出文件的工具 众所周知 在 make 中很难建模 我正在使用食谱GNU Makefile 规则从单个源文件生成一些目标 https stackoverflow com questions 2973445 gnu mak
  • 如何在 Makefile 中获取 make 命令的 pid?

    我想使用此构建特有的临时目录 如何在 Makefile 中获取 make 命令的 pid I tried TEMPDIR tmp myprog 但这似乎存储TEMPDIR as tmp myprog 然后将 eval 作为每个引用此命令的新
  • 如何在 makefile 中拥有正确的 .mod 顺序

    我正在尝试用 Fortran 为我的项目创建一个 Makefile 并使其可在现在的项目中重用 我经过多次尝试后得出的 Mkefile 如下 问题是它在少数情况下工作正常 但现在我有这个文件 main f90 初始 f90 参数 f90 函
  • makefile 目标依赖项取决于目标名称

    我有以下规则 SPECIAL file1 file2 o cpp a h CC c CFLAGS lt o 我希望如果 is in SPECIAL then b h已添加到依赖项列表中 有没有办法做到这一点 而不重复规则 您可以单独分配其他

随机推荐