✨ ** 听风八百遍,才知是人间 ** 🌏
📃个人主页:island1314
🔥个人专栏: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的语法了解
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的推导过程如下:(类似一个栈结构)
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的全部内容啦,后面我们就要讲到进度条的相关内容,就会运用到这一节的知识,敬请期待咯!!!
💞 💞 💞那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心
版权归原作者 island1314 所有, 如有侵权,请联系我们删除。