【Linux】伪目标 PHONY | 探讨项目构建问题 | Makefile | 依赖关系与依赖方法

news2024/11/16 11:52:04

  🤣 爆笑教程 👉 《看表情包学Linux》👈 猛戳订阅  🔥

💭 写在前面本章我们要学习的是 makefile。会不会写 makefile,从一个侧面说明一个人是否具备完成大型工程的能力。一个工程中的源文件不计其数,按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译 ,甚至于更复杂的功能操作。话不多说,让我们开始吧!


Ⅰ.  初识 Makefile

0x00 引入:聊一聊项目构建的话题

 项目构建这个话题说人话就是:关于你的项目和编译的话题。

对于项目构建的话题,一直用 VS 的同学可能会感叹道:

" 诶……我就从来就没这样的困扰,我在 VS 下创建多文件,运行直接特喵的 Ctrl + F5 就完事了 "

确实,编译时你需要按个键就能做到编译调试运行,编译个项目真的轻轻松松。

 那是因为 VS 帮你自动维护了对应的项目结构!

❓ 思考:你可以试着思考一下下面的问题

  • 多文件 .c,\, .h,\, .cpp  我们先编译哪一个程序?
  • 链接需要哪些库?
  • 库和头文件等在哪里找?
  • 整个项目结构,该如何维护?

上述问题你在 VS 下压根就没关心过,因为 VS 下都帮我们屁股擦得干干净净 ~

你只需要在你的 VS 下需要.c 就创建 .c,需要.h就创建 .h

需要包含头文件就 #include 头文件,因为 VS 自动帮你维护了项目的结构

也不需要牵扯先编哪个后编哪个的话题,但是在 Linux 下这些东西该如何去弄呢?

💭 举个例子:

没有请问你要先编哪个后编哪个呢?最后怎么形成可执行程序呢?

若尚未学习 makefile,你怕不是会用 gcc 命令一个个去编,编完之后还要把它们链接起来。

" 啊这,这样成本也太高了,太烦了吧……"

如果你项目中存在 100 个 .c ,70 个 .h,你在 VS 下编译直接运行就行,各种调各种编都可以。

但是如果这是在 Linux 中,有这么多的头文件和源文件,你就得把这些源文件一个个变成 .o

然后再一个个把 .o 链接起来形成可执行,头文件如果找不到的话还得自己去处理……

这些事在 Linux 中就需要我们去做了,为了能让这样的工作更适合地球人,于是就有了 makefile。

不是集成环境也没关系,我们可以使用 Makefile 来统一解决这样的问题。

0x01 什么是 Makefile

什么是 Makefile?

在我们 Linux 当中就是 make / makefilemake 是一个命令,makefile 是一个文件。

(为了不增加学习的成本,前期我们只用一个头文件给大家演示,并且会讲的浅一些)

💭 演示:如何使用 Makefile

如果放到之前,我们如果想编代码会自己 gcc,但实际上写 Makefile 也要 gcc

但 Makefile 能让你不在命令行中打 gcc 命令操作,这能避免一些比较尴尬的情况。

比如下面这种 mytest.c mytest 的顺序写反了的情况:

$ gcc mytest -o mytest.c

" 这……尬的我徒手抠出一个三室一厅 "

写选项时如果把生成可执行程序与源代码搞反了,又恰巧存在一个可执行,那源代码可能就寄了:

目录下存在一个叫 mytest 的可执行,此时如果不小心把选项写反,后果就是源代码直接消失:

$ gcc mytest -o mytest.c   ❌  写反了!!!

这种场景一不小心写反,可执行程序就覆盖掉了原文件,最后导致你的源代码都没了……

由此可见,在命令行操作时如果出现误操作,就会翻车。

" 毕竟人被杀就会死,道理大家懂得都懂。"

正是因为这些悲剧的存在,使得 Makefile 的光芒愈发温暖!

" Makefile,我滴神!我好喜欢你,为了你,我要疯狂的写bug!"  

 

0x02 依赖关系与依赖方法

下面我们来正式介绍一下 Makefile,Makefile 需要两个东西,我们先介绍第一个东西。

  • 需要在你当前路径或代码路径下创建一个 "Makefile" 或 "makefile" 文件(首字母大小写均可)

我们先做一些准备工作,然后创建一个名为 makefile 的文件:

现在有了 makefile 文件,下一步就是编写这个文件!

即在这个文件中添加对应的 "依赖关系" 和 "依赖方法"。

📚 makefile:是在当前路径下的一个普通文件,它会包含两个东西

  • 依赖关系(Dependency Relationship)
  • 依赖方法(Dependent Method)

 第一次看到这两个词的朋友可能会一脸懵逼,

不如这样,我来举一个生活当中的例子帮助大家去理解它们的意思:

我们首先弄清楚一个概念,依赖是什么意思?什么是依赖?

假如你是个在校大学生,快要到月底了,这时候你可能就要打电话给你爸要生活费了。你打电话给你爸爸,说 "爸,我是你儿子。",这就是表明依赖关系。你打电话告诉你爸你是他儿子的时候,实际上你的潜台词就是 "我要依赖你"。你给你爸打电话说:"爸我是你儿子",说完就把电话一挂,对于你爸来说会一脸懵逼 —— "这孩子今天怎么了,这是被绑架了?",你爸就不太清楚了。也就是说,你在打电话时只是表明了依赖关系,但你并没有达到你想要做的目的(要下个月的生活费),所以正确的方法应该是:"爸,我是你儿子,我要下个月的生活费。",你表达了你是谁,并且要求给你打钱。

我是你儿子 —— 表明了 "依赖关系",因为依赖关系的存在,所以才能给你打钱。

打钱 —— 就是 "依赖方法",当你把依赖关系和依赖方法表明时,你就能达到要钱的目的。

依赖关系不对,依赖方法再对也没有用,比如你的舍友给你爸打电话,说:"我是你儿子的舍友,给我打钱!",你爸绝对不会打钱的。

依赖方法表明了,依赖方法不正确同样没有用,比如你打电话给你爸:说:"我是你儿子,给我打钱我要充游戏!",你爸也不会给你打钱的!

通过上面的比喻,相信你已经知道什么是依赖关系和依赖方法了,他们必须都为真。

依赖关系和依赖方法都要为真,才能达成要钱的目的!

(\textrm{DR} \, \, \wedge \textrm{ DM}) \rightarrow \textrm{makefile}

makefile 里面表明的就是依赖关系和依赖方法,按照现阶段的认识我们可以倒推一下:

Ⅱ. 实现简单的 Makefile

0x00 写一个最简单的 Makefile

💭 演示:写一个最基本的 Makefile

mytest:mytest.c
    gcc mytest.c -o mytest

至此,我们就把一个最基本的 Makefile 写完了。

我们来 cat 看一下我们写的 Makefile,第一行是依赖关系,紧接着第二行是依赖方法。

📌 注意:依赖关系后面紧跟依赖方法时前面要空一个 tab,再写依赖方法。

此时我们就有了一个最基本的 Makefile 了,我们编译 mytest.c 文件就可以不用再敲 gcc 命令了!

直接在命令行中输入:

$ make

🚩 输入效果:

输入 make,这里就自动形成了可执行程序,会帮我们从 Makefile 去找依赖关系和依赖方法。

我们来看看这小B窄汁都做了些什么 ——

 (这个 3,我就是故意写这么大的,你就说你能把我怎么办吧)

 看到这里有的人可能会觉得 —— 

"你这不是脱裤子放屁么,这不还是要输 gcc 命令吗?这和我在命令行有什么区别?"

当前让你产生这种感觉,主要是因为:

① 上面我们写的 makefile 是最简单的 makefile 了,自然和命令行没什么差别。

② 我们目前的项目结构简单,如果后面遇到大的项目结构你就知道 Makefile 有多香了。

" 哈哈,毕竟谁会想敲一百多行 gcc 呢……"

 最最最重要的是 ——

以后我们在 Linux 下编译代码就不需要敲 gcc 命令了,直接 make 就可以了。

它会给我们带来很多便捷,如果是第一次接触,现在可能还体会不到,后面慢慢就能体会到了。

0x01 项目的清理

刚才我们说的 make 就相当于 VS 下的 "生成解决方案" :

但是 VS 下好像还有 "清理解决方案" 这样的功能,那我在 Linux 下也想拥有,怎么办?

" 得不到就毁掉?不,我们得不到还可以去 Makefile 嘛!"

💭 举个例子: 

我们现在不想要这个可执行程序了,放在之前我们会直接 rm 掉这个文件。

但是假设有这样的一个场景:一个程序生产了大量的临时文件,你岂不是要疯狂的 rm

无脑删又很容易把源代码删掉,这个时候我们就可以在 Makefile 里实现 "清理解决方案" 的功能!

.PHONY:clean
clean:

  clean  没有依赖文件,也是存在依赖关系的,只不过这个  clean  没有依赖列表。

它后面的相当于是个孤儿,而  clean  被 \textrm{​{\color{Red} .PHONY}} 修饰成伪目标(这个我们下面会说)

后面的  rm -f mytest  为依赖方法,我们现在来用一下看看效果如何!

刚才我们编译用的是 make,清理我们用 make clean:

🚩 输入效果如下:

此时如果想清理代码,在命令行直接输入 $make clean 即可。

📌 注意事项:再次强调,依赖方法前面必以 tab 键开头(红色框标记处)

0x02 多文件的 makefile

我们来演示一下多文件的 makefile。

我们先创建 3 个文件,分别是 main.c,test.c 和 test.h :

vim 分别打开这三个文件,我们写一点东西进去:

此时如果我们 gcc,我们可以:

gcc -o hello main.c test.c

我们把 test.c 跟在 main.c 后面,这里不放 test.h 的原因是因为头文件在预处理阶段就已经展开到源文件中了。

现在我们开始写 makefile:

我们想生成的 hello 文件需要依赖于 main.o 和 test.o

但我们没有 .o,所以我们还需要进一步解释一下 main.o 和 test.o 是依赖谁的。

值得一提的是,下面两种写法都是可以的:

# 写全是这样的
gcc -c main.c -o main.o   
# 实际上,如果你不写后面的,makefile也会自动给你形成同名.o
gcc -c main.c   

制作好 makefile 之后,我们 make 一下看看效果如何:

效果很好,我们再把清理功能写一下:

这里我们需要删除 *.o 文件,星号代表的是通配符,意思为 " . " 左边所有的内容我不关心,

只要它以 .o 结尾的,就删掉。此外,* 和 .o 之间没有空格!

看看效果如何:

make 就相当于 VS 中的构建项目,而 make clean 就相当于是清理项目。

Ⅲ. PHONY 伪目标

0x00 .PHONY 定义伪目标

目标文件和伪目标,给我的一种感觉就是好像都是目标文件啊?这就像正规军和伪军,都是军队。

不管是目标文件还是伪目标,最终目的都是要根据自己的依赖关系执行依赖方法。

不知道你有没有观察到,我们的 makefile 有两个目标文件:

我们在 make 的时候,默认只帮我们生成 makefile 中的 mytest.c 目标文件。

而 make clean 是制作指定名称的目标文件,做清理的工作,我们首先来思考一个问题:

❓ 思考:为什么 make 的时候它总是执行第一个呢?

makefile 在自顶而下进行形成目标文件时,它可以根据你的需求形成多个目标文件。

我们这里有两个目标文件,一个是 mytest 一个是 clean,凭什么我 make 执行的是 mytest 而不是 clean?答案很简单,就凭我 mytest 是在前面写的!

如果我们把它们两的顺序换一下:

然后我们再来进行 make 看看:

所以 makefile 在形成文件时会自顶而下扫描,默认只会形式第一个目标文件,执行该依赖关系的依赖方法。 

但是我们一般还是喜欢把形成可执行程序放在前面,也就是让 makefile 默认去形成:

那这个 \textrm{​{\color{Red} .PHONY}} 到底是干什么的呢?现在我们正式介绍一下这个东西。

\textrm{​{\color{Red} .PHONY}} 是 makefile 语法格式的一个关键字,表明 "总是被执行的" 。

  • 比如 clean 被 \textrm{​{\color{Red} .PHONY}} 修饰时,表明 clean 是总是被执行的。

这个 "总是被执行" 这句话真的非常的莫名其妙,我们先通过代码来看看什么是 "总是被执行" !

0x01 总是不被执行

"总是被执行"  就意味着还有 "总是不被执行" ,我们先来看看什么是 "总是不被执行" ?

💬 现象演示:总是不被执行

我们多次 make 之后它仍然是会提示 “make: `mytest` is up to date.”  这段话。

貌似只有第一次 make 的时候才会才会帮我们形成一个全新的 mytest ,再之后多次进行 make,

就会告知你 mytest 已经是一个可执行程序了,不能再生成了。

这种现象就叫做 "总是不被执行的" ,你已经是最新的可执行程序了,干嘛要再去形成呢。

如果自己的源代码已经用最新的源代码编译链接形成可执行文件了,那么编译器就不会再帮我们再重新根据你的 makefile 再重新给你生成了。因为既然变都没变,还给你生成那岂不是既浪费时间又浪费资源?

现在你可以不用去想它是怎么做到的,我们已经演示了 "总是不被执行" 的情况了。

0x02 总是被执行

我们刚才看了 "总是不被执行" 的现象,我们试试给我们的 mytest 也用 \textrm{​{\color{Red} .PHONY}},让它从默认的 "总是不被执行" 变成 "总是被执行" 看看:

现在 mytest 也变成了 "总是被执行" 了,现在我们来看看 "总是被执行" 是个什么现象:

💬 现象演示:总是被执行

被 \textrm{​{\color{Red} .PHONY}} 标记后,我们每次 make 都会执行 gcc mytest.c -o mytest

📚 总是被执行:无论目标时间是否新旧,照样执行依赖关系。

而我们一般不太建议、喜欢把我们形成的目标文件定义成伪目标,而是喜欢把清理定义成伪目标。

这也正是为什么我们每次 make clean 都能帮我们执行清理的根本原因。

❓ 思考:那 makefile 是如何识别我的 exe/bin 文件是新的还是旧的呢?

ACM 时间包含 Access 时间、Modify 时间 与 Change 时间。

这个 Modify 和 Change 有什么区别呢?

我们知道,"文件 = 内容 + 属性"

如果是内容发生改变,就是 Modify;如果是属性发生改变,就是 Change。

修改内容也有可能引发 Change 的改变,因为修改内容可能会引起 change time 的变化

我们打开文件修改,Access 应不应该改变呢?我们读取 Access 变不变?

要变的!但是现在不会变!因为访问文件的频率是最高的,Modify 和 Change 是不得不变的,不变的化文件就不对了。但是我们大多数情况修改文件属性和修改文件内容是很低频的事情,但打开文件是非常高平的事情,Linux 后期内核对 Access 进行了优化,将文件打开访问,打开时间不会变化,累计一段时间后他才会变化。如果不这样,打开文件这种高频率的事情,一旦更新 Access 时间,就要将数据刷新到磁盘上,这实际上一个很没效率的事情。

具体 Access 的调整策略取决于 Linux 的版本。

💡 答案:通过对比你的源文件和可执行程序的更改时间 (modify time) 识别的新旧。 根据原文件和可执行程序的最近修改时间,评估要不要重新生成。

现在我们再回头看刚才的问题:什么是 "总是被执行呢" ?

"总是被执行" 就是 忽略对比时间 (modify time),不看新旧,我让你执行你就给我执行。

📌 [ 笔者 ]   王亦优
📃 [ 更新 ]   2023.1.3
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

📜 参考资料 

C++reference[EB/OL]. []. http://www.cplusplus.com/reference/.

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

百度百科[EB/OL]. []. https://baike.baidu.com/.

比特科技. Linux[EB/OL]. 2021[2021.8.31 xi

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vector - VT System - 板卡_VT1004

今天我们来聊一下导入和测量模块VT1004版本,我们从它的技术参数、通道介绍、功能介绍几个方面来全面的介绍这块板卡,废话不多说,我们直接来看这2块板卡吧。 测量模块 - VT1004 通道功能介绍: >通过继电器切换到原始负载和母线…

Qt扫盲-QSet理论总结

QSet理论总结一、概述二、使用1. 声明2. 插入元素3. 遍历元素4. 删除元素5. 集合的运算6. 其他一、概述 QSet是Qt的通用容器类之一。俗称一个集合。QSet会按未指定的顺序存储值,也就是随机存值的方式,并提供非常快速的值查找。在内部,QSet实…

python学习|第二天

文章目录1.函数函数调用函数返回值函数参数2.bug常见类型粗心类型知识点不扎实思路不清被动掉坑常见异常类型3.文件的读写打开模式文件对象常用方法with方法4.os模块操作目录相关函数5.打包成可执行文件1.函数 函数调用 p89,笔记待补 函数返回值 1)如…

微信小程序开发过程整理

目录1微信开发相关介绍1.1微信公众平台1.2微信开放平台1.3注意事项2微信小程序开发整体介绍2.1微信小程序简介2.2小程序接入流程3框架简介3.1uni-app简介3.2学习使用uni-app3.3学习微信小程序开发4开发规范5开发示例5.1开发工具5.2开发调试5.2.1导入代码5.2.2项目运行5.2.3在微…

java常见题3

11.二分查找的次数 奇数取 中间那一个作为中值 偶数个取 中间靠左 然后不断模拟这个算法 查找的最多次数:n个元素里最多查找log二N 个元素Log2 128 7 12.equals和hashCode java.lang.Object类中有两个非常重要的方法: public boolean equals(Obje…

YOLOV5模型训练

之前在博文中讲到了YOLOV5的运行,以及转tensorrt. 但是, 一个模型通常需要结合数据训练,才能得到更好的结果. 因此,我们有必要熟悉yolov5的训练过程. 执行训练的过程 Yolov5的github提供了官方的训练脚本. 第一次运行,会自动下载数据集,然后会检测到你的gpu配置,如果不对,…

【数据结构】树

树(Tree) 知识框架 树的定义 树和图一样都是非线性结构,树是n个结点的有限集合,当n0时,称这棵树为空树。 非空树有以下特征: 有且仅有一个称为根的结点。如果n>1, 除根结点以外其它结点可以分为m(m>0)个不相交的集合T1,T…

E4445A频谱分析仪

18320918653 E4445A 名称:E4445A 频谱分析仪, 3 Hz - 13.2 GHz 详细:主要技术指标 性能 /-0.24 dB幅度精度 -155 dBm/Hz显示的平均噪声电平(DNAL) 10 kHz偏置时的相噪:-118 dBc/Hz 81 dB W-CDMA AC…

春节倒计时,让我来秀一手:用Python制作一个对联生成器

前言 跨年跨完了,马上就要迎来春节了,这不得秀一手? 那就直接开始春节的表演呗 勉勉强强来用python制作对联生成器吧 效果展示 这里的话,你自己想要啥春联主题是可以搜索滴,有些地方也是可以看着改的,…

FPGA知识汇集-FPGA的低功耗设计方法总结

精确的热分析在很多电子产品设计中都有着举足轻重的作用,在高端的PCB设计中尤为突出。热分析的结果常常会影响PCB的机械层设计和产品的外壳设计:是否需要安装散热片、散热风扇等。如果安装散热风扇,往往需要降低其噪音,这将使得机械层设计变得…

【OpenAI】What Is ChatGPT

文章目录介绍注册介绍 OpenAI发布了一个全新的聊天机器人模型—— ChatGPT,同时这也是继GPT-3.5 系列的主力模型之一 ChatGPT 测试地址: https://chat.openai.com/auth/login https://gpt.chatapi.art/ ChatGPT官方说明: Optimizing Langua…

【自学Java】Java语言数组遍历

Java语言数组遍历 Java语言数组遍历教程 Java 语言 中如果我们定义好了数组,并且给数组设置了值,那么怎么样访问数组呢?怎么样获取数组里面的数据值呢?我们可以使用 for 来遍历数组,获取每个位置上的值。 Java语言数…

谷粒学院——第十四章、微信扫码登录

准备工作 注册开发者资质 官网:https://open.weixin.qq.com/ 尚硅谷分享 wx:open:# 微信开放平台 appidappid: wxed9954c01bb89b47# 微信开放平台 appsecretappsecret: a7482517235173ddb4083788de60b90e# 微信开放平台 重定向url(guli.shop需要在微…

腾讯云存储

文章目录一、开通腾讯云存储1.注册腾讯云账号,开通对象服务2. 创建存储桶3.获取SecretId 和 SecretKey提示:以下是本篇文章正文内容,下面案例可供参考 一、开通腾讯云存储 在项目钟的图片以及文件需要归档存储。如果归档文件只保存到服务器…

WebRTC学习总结

WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流…

二十五、Docker (1)

🌻🌻 目录一、Docker的概述1.1 为什么要去学习Docker1.2 Docker 概述1.3 Docker的历史1.4 Docker 官网1.5 Docker能做什么1.6 DevOps(开发、运维)二、Docker安装启动(官网)2.1 Docker 架构2.1.1 镜像(image)2.1.2 容器…

DICOM 图像传输:使用 LeadTools 实现 C-Store SCP 服务

文章目录开发环境创建 Qt Widgets 程序设计界面配置 LeadTools 路径编写代码使用 LDicomNet 实现 SCP 的步骤日志输出编写 SCP Server 类编写 SCP Client 类启动 LDicomNet 及启动监听编译程序运行程序发布与部署测试程序界面美化参考开发环境 LeadTools 17Qt 5.15.2 MSVC2019…

【Linux】进程间通信(万字详解) —— 上篇

🎇Linux: 博客主页:一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限,出现错误希望大家不吝赐教分享给大家一句我很喜欢的话: 看似不起波澜的日复一日,一定会在某一天让你看见坚持…

谷粒学院——第九章、阿里云视频点播

阿里云视频点播 开通 地址: 上传测试 开通以后,点击控制台,然后选择音/视频: 注意:先点击启用存储地址再上传。 添加转码模版: 开发文档 官方地址:https://help.aliyun.com/p…

批量统计不同块的数量

CAD收集块的数量一般采用FI等命令,或者使用天正等软件,这些方法或多或少都存在某些问题。这时就可以编写插件满足不同场景的使用。已应用到实际工作中。 一、界面及其功能 采用c#制作cad插件,框选待统计范围,直接输出到表格中&…