【Linux系统】—— make/makefile

【Linux系统】------ make/makefile

  • [1 什么是 make/makefile](#1 什么是 make/makefile)
  • [2 第一版本makefile](#2 第一版本makefile)
  • [3 依赖关系和依赖方法](#3 依赖关系和依赖方法)
  • [4 清理](#4 清理)
    • [4.1 清理的基本语法](#4.1 清理的基本语法)
    • [4.2 make 的默认执行](#4.2 make 的默认执行)
    • [4.3 为什么要加 『.PHONY:clean』](#4.3 为什么要加 『.PHONY:clean』)
      • [4.3.1 『.PHONY:clean』的功能](#4.3.1 『.PHONY:clean』的功能)
      • [4.3.2 如何理解总是不被执行](#4.3.2 如何理解总是不被执行)
      • [4.3.2 如何区分文件的新旧](#4.3.2 如何区分文件的新旧)
  • [5 第二版本makefile](#5 第二版本makefile)
  • [6 第三版本makefile](#6 第三版本makefile)
  • [7 终版makefile](#7 终版makefile)

1 什么是 make/makefile

一句话概括:

  • make 是一个命令

  • makefile 是一个文件

  • 会不会写 makefile,侧面说明了一个人是否具备完成大型工程的能力

  • 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作

  • makefile 带来的好处就是 "自动化编译",一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大提高了软件开发的效率

  • make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。可见,makefile 都成为了一种在工程方面的编译方法

  • make 是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。

  

  

2 第一版本makefile

  在学习 make/makefile 之前,我们先来简单看看它的功能和使用方法

  1. 先创建一个 code.c 的源文件,输入代码:
c 复制代码
#include<stdio.h>

int main()
{
	printf("Hello Linux!\n");
	return 0;
}

  

  1. 在当前路径下创建一个 makefile 文件
c 复制代码
touch makefile/Makefile

注:makefile 与 Makefile 都行,不区分大小写。后文统一用 makefile 代替 makefile/Makefile


  

  1. 用vim打开makefile文件,输入以下内容

  

  1. 编译

   makefile 写好后,我们就不用再写 gcc 命令了,可以直接用 make 命令


  
输入make命令:

  

  输入make命令后,它就会在当前的工作路径找 makefile 文件,再根据 makefile 文件中的内容,自动推导自动编译

  

  此时可执行 code.exe 就生成了

  

3 依赖关系和依赖方法

  我们回到 makefile 文件中的那两行内容

  我们将第一行称之为 依赖关系,第二行必须以 tab 键开头,后面的内容我们称之为 依赖方法。

  

  下面举个例子帮大家更好理解依赖关系和依赖方法:

  月底了,你没有生活费了,你给你父亲打电话,说:"爸,我是你儿子。"这句话就在和你的父亲表明了依赖关系 。意思是爸,我是你儿子,我依赖于你。然后呢?生活费还是没要到,因为现在和你爸表面了依赖关系。这时你又说:"爸,我没生活费了,给我打点钱"。"打钱"就是依赖方法。最后,你父亲听了,很爽快地给你打了钱。

  不仅仅是计算机,现实生活中,要做成一件事,都是要具备依赖关系和依赖方法的。你和你父亲表明了依赖关系和依赖方法,你父亲才给你打钱。为什么你不给你舍友的父亲打电话呢?因为你和你舍友的父亲没有依赖关系。依赖关系错了,事就办不成。"你是我爸,你要帮我考试",这时依赖关系是对的,但依赖方法错了,事也办不成

  makefile 文件中:code.exe 说"我要形成要依赖 code.c ",可依赖怎么依赖呢?gcc 还是 g++?所以我要用 gcc 方法来编译形成可执行,两个依赖缺一不可

  

  makefile 最基本的语法前文已经初步介绍,这里就不再赘述,大家直接看图即可

  

  

4 清理

4.1 清理的基本语法

  我们用 make/makefile 来自动化清理 code.exe 文件的格式如下:

  

4.2 make 的默认执行

  makefile文件内容如下:

c 复制代码
code.exe:code.c
	gcc -o code.exe code.c
     
.PHONY:clean 
clean:
	rm -f code.exe

  

  用 make 命令生成可执行和清理:

  

  

  为什么执行 make 时,默认我们的 make 就是形成 code.exe 的可执行文件,而执行clean 时,需要 make clean 呢。code.exe 是一种目标文件,而 clean 也是一种目标文件,只是它的方法是删除文件而已,他们的本质是一样的。

  我们将makefile中的内容颠倒一下

c 复制代码
.PHONY:clean
clean:
     rm -f code.exe
 
code.exe:code.c
    gcc -o code.exe code.c

  此时默认就是执行 clean 了

  总结:make 扫描 makefile 文件时,从上往下扫描,默认形成第一个目标文件。

  所以写makefile时,我们一般喜欢把要形成的可执行程序放在最前面

  

4.3 为什么要加 『.PHONY:clean』

4.3.1 『.PHONY:clean』的功能

  .PHONY:clean到底是什么东西呢

  假设我们将.PHONY:clean这个符号声明去掉

c 复制代码
code.exe:code.c
    gcc -o code.exe code.c

clean:
    rm -f code.exe

  

  再执行相应指令

  发现并不影响 makefile 的具体操作,那为什么还要加上.PHONY:clean来声明他是一个伪目标呢?

  .PHONY其实是一种建议性的关键字,来建议 clean。它的作用是:表示目标总是被执行。谁总是被执行?对应的依赖关系和依赖方法 总是被执行。

  

4.3.2 如何理解总是不被执行

  那既然有总是被执行,肯定就有总是不被执行。我们先来理解什么叫做总是不被执行?

  当前我们的 makefile 总共有两个目标文件:code.exe 和 clean

  我们重复执行 code.exe 发现只有第一次可以执行

  

  我们打开 makefile,在 code.exe 前面加上.PHONY修饰

c 复制代码
.PHONY:code.exe
code.exe:code.c
    gcc -o code.exe code.c

.PHONY:clean
clean:
    rm -f code.exe

  此时就可以code.exe就可以总是被执行了

  

  为什么我们的可执行文件一般不建议用 .PHONY 修饰

  道理很简单,因为我们的源代码没有被更新过,没有被更新过就没有必要重新生成一份 code.exe 可执行程序,只有源文件该过了才会再次形成可执行。

  比如某个工程一共依赖一千个源文件,现在修改了其中10个,我们只需要将那10个重新编译就可以了,这样可以大大提高效率

  

  make/makefile默认老代码不重新编译

  

4.3.2 如何区分文件的新旧

  那问题又来了:make 是怎么知道可执行文件和源文件的新旧的呢?

  曾经我们提过:文件属性有三个时间:AccessModifyChange三个时间

我们知道,文件 == 内容 + 属性。对文件的修改,要么修改文件的内容,要么修改文件的属性,要么同时修改

  • 只查看文件内容:更新Access时间
  • 只修改文件内容:更新Modify时间
  • 只修改文件属性:更新Change时间

  往往我们修改文件的内容,Modify 时间和 Change 时间都会改变。因为我们往文件中写东西,文件的大小改变了,文件的大小属于文件的属性;而且时间本身就是文件的属性,修改了文件内容 Modify 时间这个属性被修改,Change 时间也会修改

  如果只想改变属性呢?可以用chmod指令只改变文件的属性。

  

  Access时间有点特殊,我们来查看一个文件看看

  可以看到,Access时间没有任何变化。为什么呢?

  因为对一个文件进行操作,查找的比重是比修改的比重大的多的 。可能访问一个文件 100 下,90 都是查找,剩下的才是修改。如果 Linux 中,只要你查了一下文件内容就更新一下时间,这就会带来一个隐性的成本:带来很多的 I/O 操作,因为时间是由操作系统自动形成的,而文件是在磁盘上存的。

  因此只有查看文件的次数达到若干次,Linux 系统才会将文件的 Access 时间更新。具体次数与特定的 Linux 版本有关。

  

  回到开头:make 是怎么知道可执行文件和源文件的新旧的呢?

  很简单,只需要比较可执行文件和源文件的Modify时间即可。如果源文件的 Modify 时间可执行的 Modify 时间晚,说明源文件被更新过。

  

  我们前面 touch 命令,认为他是用来创建文件的,其实 touch 的核心作用是用来修改文件的时间(三个时间统一更新)

  

  所以我们可以将 code.c 文件 touch 一下,再用 make 命令就可以再次编译了,这里就不再演示了。

  

  现在 .PHONY 的作用就很好理解了:

  被 .PHONY 修饰的符号,表示它总是被执行。本质是 make 在调用相应的命令时,忽略对比时间,直接执行

  

  

5 第二版本makefile

  实践中,我们是很少像前面那样写 makefile 的,我们往往是将 .o 文件编译成可执行,而不是直接编译 .c 文件。下面我们来正式学习一下如何使用 makefile

  先来看依赖链

  

   现在有了多组依赖关系,如何将.c 变成 .i 呢,.i 如何变成 .s 呢?我们需再加上依赖方法

  

  我们再加上 clean,第二版的 makefile 就写完了

cpp 复制代码
code.exe:code.o
    gcc code.o -o code.exe
code.o:code.s
    gcc -c code.s -o code.o
code.s:code.i
    gcc -S code.i -o code.s
code.i:code.c
    gcc -E code.c -o code.i

.PHONY:clean
clean:
    rm -f *.i *.s *.o *.exe

  

  实际上 makefile 在翻译时,在自己内部会维护一种类似于栈之类的东西

  makefile 文件会被 make 命令从上到下进行扫描。首先根据第一个依赖关系,知道code.exe 依赖 code.o,可 code.o 根本不存在,此时就会将这组依赖关系的依赖方法入栈 gcc code.o -o code.exe;make 命令再继续向下扫描第二组依赖关系,同理,将他们的依赖方法 gcc -c code.s -o code.o 入栈,之后再将 gcc -S code.i -o code.s 入栈。

  最后找到 .i,发现 .i 依赖 .c,当前目录时有 .c 的,此时 make 开始执行对应的依赖方法 gcc -E code.c -o code.i,再将在栈中的三个依赖方法依次出栈并执行

  

  如此便完成了从上到下扫描,从下到上执行,以形成最终的目标文件。这个过程是 makefile 进行目标文件推导的最基本规则

  

  

6 第三版本makefile

  实践中,也很少人会写第二版本的 makefile,我们来写个第三版

  makefile 允许我们定义变量,我们先写段测试命令来看看变量的用法

执行结果:

  我们可以将变量理解成宏


  

  有了变量后,我们就可以将各种具体的符号、指令、文件等都以变量的形式呈现

cpp 复制代码
BIN=code.exe
CC=gcc
SRC=code.c
FLAGS=-o
RM=rm -f

$(BIN):$(SRC)
    $(CC) $(FLAGS) $(BIN) $(SRC)

.PHONY:clean
clean:
    $(RM) $(BIN)

  

  上述的写法有点不优雅,我们可以运用 @^


  

  用make编译时,我们不想回显gcc -o code.exe code.c命令,我们可以在依赖方法前加 @,可是这样的话我就不知道编译完成了没,怎么办呢?依赖关系只有一条,可是依赖方法可以有多行,我们可以这样多加一个行依赖方法来进行字符串显示

  

  

  

7 终版makefile

  前三个版本,我们都是将单一文件编成可执行,如果有多个文件怎么办呢?

  根据编程习惯,我们往往会先将所有的 .c 编译形成 .o,再将所有的 .o 一起编译形成可执行

  那么这样就可以了吗,还差最后一步

  上面的写法只是将所有的 .c 编成了.o,可最终编成可执行时,变量 OBJ 还是指定的 code.o,因此此时只有 code.o 单独编成可执行,我们想的是多个 .o 一起链接形成可执行。


  

  首先,在SRC获取源文件上,我们不用自己指定 .c 文件。自动获取路径中的 .c 文件有两种方法

法一:

  makefile 是可以直接执行命令行命令的,这种写法的意思是SRC获取 ls 命令在 shell 中的执行结果,即 make 扫描到的文件全部放在了SRC变量中

c 复制代码
SRC=$(shell ls)

我们来试验一下

c 复制代码
.PHONY:test
test:
    @echo $(SRC)

  这样如果我们再加上通配符就可以获取当前路径中所有的 .c 文件啦


  

法二:

  makefile 中包含类似于函数的东西。wildcard 可以将当前路径下符合条件的文件通配出来

c 复制代码
SRC=$(wildcard *.c)

  

  此时我们已经获取了当前路径的所有 .c,那 .o 怎么获取呢?

  .o 不就是对应的源文件的 .c 换成 .o 吗?因此 .o 的获取很简单

cpp 复制代码
OBJ=$(SRC:.c=.o)

  此时的OBJ表示的是将所有SRC中的 .c 换成 .o

  

我们来测试一下:

cpp 复制代码
.PHONY:test
test:
    @echo $(SRC)
    @echo $(OBJ)

  

  这样,最终版的 makefile 就完成啦,我们还可以顺便加上 clean

  

  

  大家构建100个源文件来进行测试,输入指令:count=1; while [ $count -le 100 ]; do touch code${count}.c; let count++; done创建 100 个源文件,再输入 make 指令进行编译即可

  输入 make 指令后,发现 make 自动将所有的 .c 编译成 .o 再将所有的 .o 一起链接成可执行文件。大家可以自己回去试一试。

  

  

  

  


  好啦,本期关于 make/makefile 的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 Linux 的学习路上一起进步!

相关推荐
大树886 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠6 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质6 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush46 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5207 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz7 小时前
Maven依赖冲突
java·服务器·maven
Inhand陈工7 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智8 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩8 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
shushangyun_8 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化