一、前言
在上一讲中,我们介绍了Linux下的编译器 - gcc/g++的使用,本节我们来介绍一下如何使用make/Makefile实现项目的自动化构建
- 知道了如何在Linux上编译C语言代码,而且清楚了可执行文件
a.out
的由来,是从
test.c
经过预编译到test.i
test.i
经过编译到test.s
test.s
经过汇编到test.o
test.o
经过链接到a.out
- 可是看着上面的这些编译 + 链接的过程,是不是觉得很冗余复杂,我们平常做做练习还好,但若是到了那些==大型工程中,可是具有上千、上万条代码==,若是一次编译完成之后又修改了源代码,接着又想进行编译,此时便需要==重新敲入指令==,那会使得工作量变得很大。可是在VS中,我们可以无限地修改自己的代码,然后随时编译运行,不需要考虑这些复杂的原理
- 那Linux中有没有这样的一站式操作呢,那就是【make/Makefile】👈
二、make/Makefile背景介绍
首先我们来介绍一下什么是make/Makefile,以及它们之间的关系
1、Makefile是干什么的?
- Makefile 是一个
文件
。它是一个工程文件的编译规则,它记录了原始码如何编译的详细信息、描述了整个工程的编译链接等规则。 - Makefile 带来的好处就是——“自动化编译"。一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率🚀
:dart:先来看一下Makefile的【语法】:
target(目标文件):文件1 文件2(依赖文件列表) //依赖关系 <Tab>gcc -o 欲建立的执行文件 目标文件1 目标文件2 ///依赖方法 command ... ...
- target就是我们想要建立的信息,一般称作
目标文件
。而后面的依赖文件列表
就是具有相关性的 object files,也就是目标文件所依赖的文件(可以是一个或多个,也可以没有)
:dart:然后看一下Makefile的【规则】:
- 目标文件与依赖文件列表文件之间要使用冒号隔开
目标文件:依赖文件列表
- target可以是一个目标文件、执行文件,甚至可以是一个标签【后面会提到的伪目标】
- 依赖方法前面必须加Tab空格键
- 依赖方法以gcc为例,也可以是其他的shell指令【command】
==会不会写Makefile ,从一个侧面说明了一个人是否具备完成大型工程的能力==💪
2、make又是什么?
- make是一个
命令工具
,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法
【总结一下】:make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建👈
三、demo实现【见见猪跑🐖】
了解可什么是make/Makefile之后,我们就来用一用它们
- 刚才说到make命令是用来解释Makefile中指令的,所以我们需要创建一个。我喜欢用大写的Makefile,当然你写成makefile也是可以的
- 这里直接
vim Makefile
即可,进入编辑界面,如果有不懂vim的,可以看看我的文章 —— 人生苦短,我用Vim
- 此时我们可以再创建一个
test.c
,作为源文件,然后开始往里面写内容 - 现在我要通过gcc去编译这个test.c的文件,然后生成一个我自己命名的【mytest】这个可执行文件,此时我们只需要在Makefile写上这两行即可
mytest:test.c gcc -o mytest test.c
- 然后回到命令行,我们来执行一下这个
make
指令,它就是自动在当前源文件的所在路径下搜寻Makefile,并解释里面命令。之后若是我们需要去编译任何文件,只需要在Makefile里面做一个添加即可,怎么样,是不是很方便
三、依赖关系与依赖方法
通过小小的demo,想必你已经感受到了【自动化构建工具】的强大,我们来仔细看看Makefile中的指令为何要如此书写✍
1、概念理清
- 对于
mytest:test.c
,冒号左侧是目标文件,右侧是它的依赖文件,所以就可以说它们之间存在一种【依赖关系】,只有test.c存在才可以有mytest - 那要如何通过test.c去生成mytest呢❓ 此时就需要使用到下面的这句gcc指令
gcc -o mytest test.c
👉它叫做【依赖方法】
2、感性理解【父与子】
就这么说还是不太好理解,我们举个父与子的生活小案例来帮助理解
今天呢,是这个月的28号的了,家里面在月初给你的2000块钱差不多也花完了,于是这两天只能吃土,此时你打开微信后看到和老爸的聊天框,于是就想着和老爸要点钱【毕竟儿子向父亲要钱天经地义😀】
- 这里的老爸和儿子指的就是
[依赖关系]
- 儿子向老爸要钱指的就是
[依赖方法]
==下面辨析几种依赖关系与依赖方法==
❌错误的依赖方法
- 此时你打电话和你老爸说:“我是你儿子,你帮我写作业。”
- 以上这句话就是依赖关系正确,但是依赖方法不正确。老爸帮儿子写作业无法执行
所以只有依赖关系不行,还得有正确的依赖方法
❌错误的依赖关系
- 此时你拿起室友的手机和他爸爸打电话说:“我是你儿子,你给我点零花钱。”
- 以上这句话就是依赖方法正确,但是依赖关系不正确。因为别人的老爸没义务给你钱
依赖方法对了,但是依赖关系不对也不行
✔ 正确的依赖关系与依赖方法
- 经历了种种挫折后,你重新拿起手机说:“老爸,我是你儿子,可以给我点零花钱吗?”
- 上面这种说法就是完全正确的依赖关系与依赖方法
完成一件事,必须得有正确的依赖关系 + 正确的依赖方法
3、深层理解【程序的翻译环境 + 栈的原理】
看完了【依赖关系】与【依赖方法】的感性理解,相信你对它们有了一定程度的认识,接下去深入地来了解一下它们之间的关系
1 mytest:test.o 2 gcc test.o -o mytest
- 开头提到过源程序是如何经过一步步的编译来形成可执行文件的吗,因为可执行文件
mytest
是依赖于汇编后的目标文件test.o
的,但是现在我们没有这个文件,因此就要去倒推一下如何获取这个test.o
- 对于
test.o
来说,它依赖于test.s
这个经过编译之后文件,可是【test.s】不存在,所以跳转到下一条依赖关系
3 test.o:test.s 4 gcc -c test.s -o test.o
- 对于
test.s
来说,它依赖于test.i
这个经过预编译之后的文件,可是【test.i】不存在,所以跳转到下一条依赖关系
5 test.s:test.i 6 gcc -S test.i -o test.s
- 对于
test.i
来说,它依赖于test.c
这个源文件,查找后发现源文件存在,于是开始执行gcc命令
7 test.i:test.c 8 gcc -E test.c -o test.i
以下就是我们需要在Makefile中修改的【依赖关系】与【依赖方法】
- 最后来到命令行中执行一下【make】命令,便完成了所有的编译,对于之前一步步地写这个编译的过程,真的是来得方便很多
- 因为只有当执行完了最后一条命令后生成了【test.i】的文件之后才可以一一往上继续执行,这种反着来的执行逻辑就相当于是我们在数据结构中学过的栈,有一个先进后出的逻辑
- 而这种匹配的过程又更像是我们之前所讲到过的一道题有效的括号,若是左括号就入栈,若是碰到右括号就进行一个出栈匹配
【总结一下】:在[依赖关系]
中,若是目标文件所依赖的文件不存在,就将这个依赖方法
入栈,转到下一组[依赖关系]
,依次循环往复,直到当前目标文件所依赖的文件存在时,就进行出栈,开始执行依赖方法
。最后获取的便是那个我们最初想要的目标文件