✨ 听风八百遍,才知是人间 🌏
🔥个人专栏:Linux---登神长阶
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
🚀前言
项目构建时遇到的各种挑战如文件编译顺序、库链接、依赖文件的管理等,在不同开发环境中会有不同的解决方案。
在 Visual Studio (VS) 环境中,这些问题往往被自动处理,运行直接 Ctrl + F5就可以了,编译个项目非常轻轻松松。 但那是因为 VS 帮你自动维护了对应的项目结构!
那如果我们需要去手动实现呢:多文件 我们应该先编译哪一个程序?链接需要哪些库?整个项目结构,该如何维护......在 Linux 环境中,我们需要更手动、细致地管理这些方面。为了解决这个问题,Linux 提供了自动化构建工具 Makefile。
1. Make和Makefile的基本概念
🎈1.1 Make是什么?
- make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi 的 make,Visual C++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法。
🎈1.2 Makefile是什么?
- Makefile 是一个 文件。它是一个工程文件的编译规则,它记录了原始码如何编译的详细信息、描述了整个工程的编译链接等规则。
- Makefile 是 Linux 下用于管理文件依赖和编译顺序的一个重要工具。它用于定义项目中的各个源文件如何编译链接,可以极大地提高开发效率。
- Makefile 带来的好处就是------"自动化编译" 。一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率🔥🔥
🎈1.3 Makefile的语法了解
cpp
target(目标文件):文件1 文件2(依赖文件列表) //依赖关系
<Tab>gcc -o 欲建立的执行文件 目标文件1 目标文件2 ///依赖方法
command
...
...
target就是我们想要建立的信息,一般称作
目标文件
。而后面的依赖文件列表就是具有相关性的 object files,也就是目标文件所依赖的文件(可以是一个或多个,也可以没有)
简述一下其基本的语法规则:
- 目标文件与依赖文件列表文件之间要使用 :(冒号) 来隔开目标文件:依赖文件列表。
- target可以是一个目标文件、执行文件,甚至可以是一个标签【后面会提到的伪目标】。
- 依赖方法前面必须加Tab空格键。
- 依赖方法以gcc 为例,也可以是其他的shell指令【command】。
综上所述:
我们可以了解到Makefile 文件 中定义了一系列规则,指定文件编译顺序、文件依赖关系及各文件的编译方法。而 make 命令是一个解释 Makefile 文件的命令工具,可以完成项目的自动化构建。
2. Make和Makefile的使用
2.1 基本使用
- 创建Makefile:
touch Makefile
- 编写Makefile:
vim Makefile
- 编写要生成的可执行程序mybin和项目清理clean:
(注:clean 只是我们声明出来的名字,当然也可以声明为其他的)
注意:
- 对于test:test.c ,冒号左侧是目标文件 ,右侧是它的依赖文件 ,所以就可以说它们之间存在一种 【依赖关系】,只有 test.c 存在才可以有 test。
- 那要如何通过 test.c去生成 test 呢❓ 此时就需要使用到下面的这句 gcc指令gcc -o test test.c 👉它叫做 【依赖方法】
- 使用make命令生成可执行程序
make
- 使用make命令进行项目清理
make clean
2.2 基本语法规则
Makefile 中包括依赖关系(目标 、依赖) 和依赖方法(命令) 。
下面是 Makefile 中一些要素的基本语法规则:
🧩2.2.1 第一行不空行
原因:第一行通常是一个目标,例如 all: 或者 clean:。在 Makefile 中,空行被视为分隔符,用于区分不同的规则或目标。
当 Make 工具解析 Makefile 文件时,它会忽略空行,并将第一行之后的非空行视为第一个规则或目标。如果第一行是一个空行,可能会导致 Make 工具不正确地解释 Makefile,从而产生意外的行为或错误。
🧩2.2.2 目标
**目标:**指定了要生成的文件或要执行的操作名。
例如:上面的test就是要生成的目标文件名。
🧩2.2.3 命令
命令(依赖方法):包含了生成目标所需的具体操作步骤,通常是一条或多条 Shell 命令。
第二行必须以Tab开头 ,不能是空格(注意: 按四下空格会报错**)**,紧接着是生成目标文件的命令。
例如:上面的gcc test -o test.c
🧩2.2.4 伪目标
伪目标: 伪目标是指在 Makefile 中**.PHONY** 定义的不对应实际文件的目标,通常用于执行一些特定的操作,比如清理临时文件 。
例如:上面的clean目标用于执行清理操作,删除test文件。
注:make默认执行的是第一行的命令,一般把清理工作放在最后面
🧩2.2.5 其他
注释: 使用 # 符号来添加注释,注释从 # 开始一直到该行的末尾。
变量: 可以使用变量来存储命令选项、编译器名称等信息,然后在规则中引用这些变量。
语法格式:VAR_NAME = value
取消回显: 由于调用make命令,其会默认显示回显,因此一般通过使用 **@**加在命令前面取消回显
**条件判断:**可以使用条件判断(ifeq、ifdef 等)来根据不同的条件执行不同的命令。
**函数:**Makefile 支持一些内置函数,可以用于字符串处理、文件查找等操作。
使用make和make clean,就可以方便地完成项目自动化构建和清理。
3. Makefile 工作原理
- makefile 或者Makefile文件会被make从上到下扫描,如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到"test"这个文件,并把这个文件作为最终的目标文件
- 如果test文件不存在,或是test所依赖的后面的test.o文件的文件修改时间要比test这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成test这个文件。
- 如果test所依赖的test.o文件不存在,那么make会在当前文件中找目标为test.o文件的依赖性,如果找到则再根据那一个规则生成test.o文件。(这有点像一个堆栈的过程)
- 当然,你的C文件和H文件是存在的啦,于是make会生成 test.o 文件,然后再用 test.o 文件声明make的终极任务,也就是执行文件test了。
- 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
- make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
注意:make 默认只生成一个可执行程序
🌸3.1 make/Makefile识别文件新旧
make命令不是每次都会重新编译,只有更改过的文件才会重新编译。(提高编译效率)
若源代码没有更改也重新编译,那么每次预处理编译汇编链接的时间比较长,成本高
make/Makefile是如何知道文件更改过的?
答:通过源文件的修改时间和形成的可执行程序(也是文件)的修改时间做对比。
重新编译的本质:重新写入一个二进制的可执行文件(bin文件),文件的修改时间会跟着更改。
- 第一次的时候,一定是先有源文件,才有bin文件。
源文件的修改时间 < bin文件的修改时间- 第二 ~~ N 次的时候,我们对源文件做任何修改的时候,
源文件的修改时间 > bin文件的修改时间
重新编译形成可执行
大部分情况下重新编译都没问题,问题的产生不仅仅是修改新文件就能解决的。有些历史问题需要重新清理项目才可以解决。
文件 = 内容 + 属性,所以文件的ACM时间肯定与内容或属性有关。
**Access(最近访问时间):**普通文本文件打开:cat、vim,或者对目录进入、ls显示等
**Modify (对内容修改):**当文件内容发生变化时,修改时间(mtime)会被更新。
**Change(对属性修改):**当文件的权限、所有者、链接数或文件名甚至文件大小发生变化时,更改时间(ctime)会被更新。
注:
三种时间会出现联动,例如对内容修改,Access和Change时间也会更改。
Access时间不是每次访问时都更改 ,读取查看文件操作最频繁,如果每次都改的话,比较浪费时间,因为文件一般都在磁盘存放,更改时间的本质 就是访问磁盘 。但是访问磁盘的速度比较慢(相对cpu而言),读取查看文件操作又是很频繁,如果每次都更改Access time的话,系统效率就会降低很多,所以就会隔一段时间更改一次。
(具体间隔时间和是否间隔,由内核版本决定)
使用touch命令可以修改ACM时间。
-a 选项 修改Access时间 ,但同时也修改了change时间,因为access时间也是属性。
-m 选项 修改Modify时间,但是Change时间也会跟着改。
综上,我们知道 make 是通过对比源文件和bin文件的Modify时间确定文件新旧的。
🌸3.2 .PHONY修饰的伪目标总是被执行
.PHONY配置项的目标clean并不是其他文件生成的实际文件,使make命令会自动绕过隐含规则搜索过程,也就是说执行命令make clean会自动忽略名为"clean"文件的存在,因此声明 .PHONY 配置项会改善性能,并且不需要担心实际同名文件存在与否😮。
【通俗一点说】:.PHONY 修饰的目标clean并不是某个依赖项生成的实际文件,因此make命令不再去搜寻当前文件夹下是否有clean文件,这样少去做一些事,自然会改善性能,并且不用担心当前文件夹下是否有同名的文件。
通过时间对比,可以做到不让有些代码进行重新编译(不让某些操作进行)。
如上:右边的test被.PHONY修饰,则多次make时,都会执行gcc命令,把可执行程序重新形成。
🌸3.3 make/Makefile具有依赖性的推导能力
💢 越是接近目标文件的命令,就越是要写在前面。因为程序是按照递归的方式进行依赖文件查找的,看到第一行有一个没见过的依赖文件,就往下一行进行查找,以此类推。
对于gcc的编译,我们都知道要生成一个可执行程序需要经过预处理、编译、汇编和连接 ,中间会产生.i,.o,.s文件 。但是在上面的操作中都没有生成中间文件 。
但是我们知道一件事:生成bin文件,就需要对应的.o文件。
以Makefile的推导过程如下:(类似一个栈结构)
cpp
test.o: test.s
gcc -c test.s -o test.o
test.s: test.i
gcc -S test.i -o test.s
test.i: test.c
gcc -E test.c -o test.i
.PHONY:clean
clean:
rm -f test test.o test.s test.i
但是一般都不会写成这么复杂,一般都是如下写法:
4. Makefile小知识
🍉 4.1 替换
符号 | 含义 |
---|---|
= | 替换 |
%.o | 任意的.o文件 |
%.c | 任意的.c文件 |
🥝 4.2 通配符
符号 | 含义 |
---|---|
$^ | 所有依赖文件 |
$@ | 所有目标文件 |
$< | 所有依赖文件的第一个文件 |
此外:Makefile中可以编写变量,表达式之间不建议带空格
通过 $(变量名) 来引用变量的值。
$@ 和 $^,前者表示:左侧被编译的所有内容,即【目标文件】,后者表示:之后所有内容,即【依赖文件】。
此时,当我们再去make的时候,就可以发现这个特殊符号自动替换成了:两侧的【目标文件】和【依赖文件】。
🍍4.3 生成多个可执行程序
一般来说make 默认只生成一个可执行程序如下:
但是我们可以做出以下修改:
📖总结
以上就是Make 和 Makefile的全部内容啦,后面我们就要讲到进度条的相关内容,就会运用到这一节的知识,敬请期待咯!!!
💞 💞 💞那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心