在Windows中,我们写C代码或者C++代码都需要用先找到一款合适的编译器,用来方便我们更好的完成代码,比如说vs2019,这些工具的特点是集成了多种开发所需的功能,如代码编辑、编译、调试、版本控制等,无需在不同的工具之间切换。自动化和简化了许多常见的开发任务,减少了繁琐的操作和配置。像 Visual Studio 可以自动完成代码补全、语法检查,快速定位错误,节省了开发者的时间。
而在Linux中,我们也有一个工具不用再单纯的用gcc指令来运行代码,具体情况可以看上一篇博客。而这个工具就是make,虽然不像其他编译器那样快捷,美观。但是对于Linux系统开发来说,极大的提高了开发效率。
make和makefile概念
会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。现在我们就来学习这个工具。
make概念
在 Linux 中,make
是一个工具,用于自动化编译和构建软件项目,执行该命令需要当前操作目录下有一个名为makefile
或者Makefile
的文件,在makefile
内部编写指令,随后就可以通过make
快速执行一系列的指令了。
make
的核心概念是依赖关系和规则。它通过读取一个名为 Makefile
的文件来确定项目中各个文件之间的依赖关系,以及如何根据这些依赖关系执行相应的命令来生成目标文件。
依赖关系指的是某个目标文件(比如可执行文件或库文件)的生成依赖于其他的源文件或中间文件。例如,如果一个可执行文件依赖于多个 .c
源文件和 .h
头文件,make
会检查这些依赖文件的修改时间,如果依赖文件有更新,就会重新执行相应的编译命令来生成新的目标文件。
使用方法
我先在makefile中编写一段指令,然后跟着指令来学习。
mycode:mycode.c
gcc mycode.c -o mycode
clean:
rm -f mycode
我先创建了一个mycode.c
文件,里面有一段C语言代码,然后生成的目标文件是mycode
。在makefile
文件中,发现了2段属性的代码,分别是gcc mycode.c -o mycode
,该指令可以把源文件编译为可执行文件;rm -f mycode
是删除mycode
文件。编写完成之后,我们使用make工具就可以完成源代码的编译工作了。
C语言代码执行的结果是hello Linux
,如果我再添加一段代码为printf("hello world\n");
,不用像之前一样使用gcc mycode.c -o mycode
这个指令,可以直接make
后,再用./mycode
就可以编译代码了,相比之前每次改变代码之后都需要执行gcc这条指令,让人感到很麻烦,所以make这个工具提高了我们的工作效率。
当我们执行完代码之后,想要删除mycode
文件,只用使用make clean
指令即可。
make原理
学会如何使用之后,我们来学习makefile
文件中4条指令的作用都是什么。
mycode:mycode.c
gcc mycode.c -o mycode
mycode:mycode.c
:通过:
把2个文件连接起来,叫做依赖关系。即mycode
这个目标依赖于mycode.c
文件。gcc mycode.c -o mycode
:这是依赖方法。它指定了使用gcc
编译器将mycode.c
编译成可执行文件mycode
。依赖方法前必须使用Tab
键隔开,不能使用4个空格。
下面来看后2条指令
clean:
rm -f mycode
该代码中,目标文件是clean,没有依赖文件列表,依赖关系是删除mycode
指令,只用执行依赖方法中的代码。clean
相当于vs中清理解决方案的操作。
现在我们有一个问题,
为什么执行
mycode:mycode.c
只需要make指令即可,而要删除mycode
文件,则需要使用make clean
指令呢?
根据上篇博客所学到的知识,我们把生成可执行目标文件分成4个过程。把这4个过程写进makefile
文件当中。
mycode:mycode.o
gcc mycode.o -o mycode
mycode.o:mycode.s
gcc -c mycode.s -o mycode.o
mycode.s:mycode.i
gcc -S mycode.i -o mycode.s
mycode.i:mycode.c
gcc -E mycode.c -o mycode.i
clean:
rm -f mycode.i mycode.s mycode.o mycode
该文件是一个完整的C语言程序的编译链接过程,但是执行编译的过程中,应该依次生成.i,.s,.o
三个文件,最后再生成可执行目标文件mycode
。当进行第一条依赖关系的时候,就没有.o
文件,第二个依赖关系缺少.s
文件,依次类推,整个过程都是反过来的,那么还能成功执行makefile
文件吗?
可以看出执行成功了。这是因为make会自动推导makefile中的依赖关系,就算把顺序颠倒了,也可以成功运行。
这个过程有点像递归过程,最开始只有mycode.c
文件,只有找到了源文件,才能依次生成其它文件。
如果我们把clean指令放在最前面,再使用make指令会发生什么情况呢?
由图片可以看出,会先执行clean
指令,原因是:当make后面不接任何目标时,make会从上往下找,找到第一个依赖关系并执行。不过建议还是把clean指令放在最后面。
接下来还有一个问题,
[!question]
如果源代码没有发生改动,为什么只能编译一次,多次编译后就无效了呢?这是如何做到的呢?
如图所示:
答案就是为了提高编译效率。代码没有发生改动的话,编译器只需要执行一次就可以了,因为多次编译之后结果还是一样的。试想一下,如果有一个工程文件,里面的代码有几百万行,编译一次的时间就需要几十分钟,如果其他人再来编译一次,又会浪费很长时间,所以只需要执行一次就行。
接着回答第2个小问:如何做到的
答:我们知道一定是源文件(.c)形成可执行文件(.exe)。先有源文件,才有可执行,一般而言,源文件的最近修改时间比可执行文件是要老的!如果我们更改了源文件,历史上曾经还有可执行文件,那么源文件的最近修改时间,一定要比可执行程序要新的。所以只需要比较,可执行程序的最近修改时间和源文件的最近修改时间:
.exe 新与 .c :源文件是老的,不需要重新编译
.exe 老与 .c :源文件是新的,需要重新编译
那么如何进行验证呢?我们需要用到一个新指令:stat 文件名
这条指令可以查看文件的时间。
- Access:访问时间。该文件最后一次被读取或执行的时间
- Modify:修改时间(针对的是文件内容)
- Change:更改时间(针对的是文件属性)
在之前的学习中,我们知道==文件 = 文件内容 + 文件属性
==。文件属性指的是文件的名称、大小、权限、存储位置等。
由此可以得出结论:make会根据源文件和目标文件的新旧,判定是否需要重新执行依赖关系进行编译。如果想让对应的依赖关系总是被执行呢?那么就可以在makefile中添加.PHONY:文件名
指令
不过不建议用.PHONY
修饰可执行程序,一般用在清理工作当中。我们将它设置为伪目标,用 .PHONY 修饰,伪目标的特性是,总是被执行的。
.PHONY: clean
clean:
rm -f mycode
makefile的特殊处理
mycode:mycode.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f mycode
$@
等价于冒号左边,$^
等价于冒号右边
如果在依赖方法前面加个@
符合,作用是不用显示gcc指令。