【Linux系统】—— make/makefile

embedded/2025/2/4 16:50:12/

【Linux系统】—— make/makefile

  • 1 什么是 make/makefile
  • 2 第一版本makefile
  • 3 依赖关系和依赖方法
  • 4 清理
    • 4.1 清理的基本语法
    • 4.2 make 的默认执行
    • 4.3 为什么要加 『.PHONY:clean』
      • 4.3.1 『.PHONY:clean』的功能
      • 4.3.2 如何理解总是不被执行
      • 4.3.2 如何区分文件的新旧
  • 5 第二版本makefile
  • 6 第三版本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 的源文件,输入代码:
#include<stdio.h>int main()
{printf("Hello Linux!\n");return 0;
}

  
2. 在当前路径下创建一个 makefile 文件

touch makefile/Makefile

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


  

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

在这里插入图片描述


  
4. 编译
   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文件内容如下:

code.exe:code.cgcc -o code.exe code.c.PHONY:clean 
clean:rm -f code.exe

  

  用 make 命令生成可执行和清理:
  
  在这里插入图片描述

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

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

.PHONY:clean
clean:rm -f code.execode.exe:code.cgcc -o code.exe code.c

在这里插入图片描述

  此时默认就是执行 clean 了

  总结make 扫描 makefile 文件时,从上往下扫描,默认形成第一个目标文件。
  所以写makefile时,我们一般喜欢把要形成的可执行程序放在最前面

  

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

4.3.1 『.PHONY:clean』的功能

  .PHONY:clean到底是什么东西呢
  假设我们将.PHONY:clean这个符号声明去掉

code.exe:code.cgcc -o code.exe code.cclean:rm -f code.exe

  
  再执行相应指令

在这里插入图片描述

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

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

4.3.2 如何理解总是不被执行

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

  当前我们的 makefile 总共有两个目标文件:code.exe 和 clean
  我们重复执行 code.exe 发现只有第一次可以执行

在这里插入图片描述

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

.PHONY:code.exe
code.exe:code.cgcc -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 就写完了

code.exe:code.ogcc code.o -o code.exe
code.o:code.sgcc -c code.s -o code.o
code.s:code.igcc -S code.i -o code.s
code.i:code.cgcc -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 允许我们定义变量,我们先写段测试命令来看看变量的用法

在这里插入图片描述

执行结果:

在这里插入图片描述

  我们可以将变量理解成宏


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

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变量中

SRC=$(shell ls)

我们来试验一下

.PHONY:test
test:@echo $(SRC)

在这里插入图片描述

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


  

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

SRC=$(wildcard *.c)

在这里插入图片描述


  
  此时我们已经获取了当前路径的所有 .c,那 .o 怎么获取呢?
  .o 不就是对应的源文件的 .c 换成 .o 吗?因此 .o 的获取很简单

OBJ=$(SRC:.c=.o)

  此时的OBJ表示的是将所有SRC中的 .c 换成 .o
  
我们来测试一下:

.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 的学习路上一起进步!


http://www.ppmy.cn/embedded/159518.html

相关文章

bat脚本实现自动化漏洞挖掘

bat脚本 BAT脚本是一种批处理文件&#xff0c;可以在Windows操作系统中自动执行一系列命令。它们可以简化许多日常任务&#xff0c;如文件操作、系统配置等。 bat脚本执行命令 echo off#下面写要执行的命令 httpx 自动存活探测 echo off httpx.exe -l url.txt -o 0.txt nuc…

mac连接linux服务器

1、mac连接linux服务器 # ssh -p 22 root192.168.1.152、mac指定密码连接linux服务器 (1) 先安装sshpass,下载后解压执行 ./configure && make && makeinstall https://sourceforge.net/projects/sshpass/ (2) 连接linux # sshpass -p \/\\\[\!\\wen12\$ s…

使用Express.js和SQLite3构建简单TODO应用的后端API

使用Express.js和SQLite3构建简单TODO应用的后端API 引言环境准备代码解析1. 导入必要的模块2. 创建Express应用实例3. 设置数据库连接4. 初始化数据库表5. 配置中间件6. 定义数据接口7. 定义路由7.1 获取所有TODO项7.2 创建TODO项7.3 更新TODO项7.4 删除TODO项 8. 启动服务器 …

C++ Primer 标准库vector

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

汇编知识点汇总

汇编的组成 汇编指令 数据处理指令 数据搬移指令数据位移指令位运算指令算术运算指令比较指令 跳转指令内存读写指令状态寄存器传送指令异常产生指令协处理器指令 伪操作 在程序编译过程中起到编译引导作用的内容 .text .global .if .else .endif 伪指令 不是汇编指令&…

利用Python高效处理大规模词汇数据

在本篇博客中&#xff0c;我们将探讨如何使用Python及其强大的库来处理和分析大规模的词汇数据。我们将介绍如何从多个.pkl文件中读取数据&#xff0c;并应用一系列算法来筛选和扩展一个核心词汇列表。这个过程涉及到使用Pandas、Polars以及tqdm等库来实现高效的数据处理。 引…

SpringBoot源码解析(九):Bean定义接口体系

SpringBoot源码系列文章 SpringBoot源码解析(一)&#xff1a;SpringApplication构造方法 SpringBoot源码解析(二)&#xff1a;引导上下文DefaultBootstrapContext SpringBoot源码解析(三)&#xff1a;启动开始阶段 SpringBoot源码解析(四)&#xff1a;解析应用参数args Sp…

STM32_SD卡的SDIO通信_DMA读写

本篇&#xff0c;将使用CubeMXKeil&#xff0c;创建一个SD卡的DMA读写工程。 目录 一、简述 二、CubeMX 配置 SDIO DMA 三、Keil 编辑代码 四、实验效果 实现效果&#xff0c;如下图&#xff1a; 一、简述 上篇已简单介绍了SD、SDIO&#xff0c;本篇不再啰嗦&#xff0c;…