项目构建时遇到的各种挑战如文件编译顺序、库链接、依赖文件的管理等,在不同开发环境中会有不同的解决方案。
在 Visual Studio (VS) 环境中,这些问题往往被自动处理,运行直接 Ctrl + F5 就可以了,编译个项目非常轻轻松松。 但那是因为 VS 帮你自动维护了对应的项目结构!
那如果我们需要去手动实现呢:多文件 我们应该先编译哪一个程序?链接需要哪些库?整个项目结构,该如何维护......在 Linux 环境中,我们需要更手动、细致地管理这些方面。为了解决这个问题,Linux 提供了自动化构建工具 Makefile。
target(目标文件):文件1 文件2(依赖文件列表) //依赖关系
<Tab>gcc -o 欲建立的执行文件 目标文件1 目标文件2 ///依赖方法
command
...
...
target就是我们想要建立的信息,一般称作
目标文件
。而后面的依赖文件列表就是具有相关性的 object files,也就是目标文件所依赖的文件(可以是一个或多个,也可以没有)
简述一下其基本的语法规则:
综上所述: 我们可以了解到 Makefile 文件中定义了一系列规则,指定文件编译顺序、文件依赖关系及各文件的编译方法。而 make 命令是一个解释 Makefile 文件的命令工具,可以完成项目的自动化构建。
touch Makefile
vim Makefile
(注:clean 只是我们声明出来的名字,当然也可以声明为其他的)
注意:
make
等生成可执行程序后,再输入命令 ./filename,其中 filename 是可执行文件的名称,就可以运行程序。
make clean
Makefile 中包括依赖关系(目标、依赖)和依赖方法(命令)。 下面是 Makefile 中一些要素的基本语法规则:
原因:第一行通常是一个目标,例如 all: 或者 clean:。在 Makefile 中,空行被视为分隔符,用于区分不同的规则或目标。 当 Make 工具解析 Makefile 文件时,它会忽略空行,并将第一行之后的非空行视为第一个规则或目标。如果第一行是一个空行,可能会导致 Make 工具不正确地解释 Makefile,从而产生意外的行为或错误。
目标:指定了要生成的文件或要执行的操作名。 例如:上面的test就是要生成的目标文件名。
命令(依赖方法):包含了生成目标所需的具体操作步骤,通常是一条或多条 Shell 命令。 第二行必须以Tab开头,不能是空格(注意:按四下空格会报错),紧接着是生成目标文件的命令。 例如:上面的gcc test -o test.c
伪目标:伪目标是指在 Makefile 中.PHONY定义的不对应实际文件的目标,通常用于执行一些特定的操作,比如清理临时文件。 例如:上面的clean目标用于执行清理操作,删除test文件。 注:make默认执行的是第一行的命令,一般把清理工作放在最后面
注释:使用 # 符号来添加注释,注释从 # 开始一直到该行的末尾。 变量:可以使用变量来存储命令选项、编译器名称等信息,然后在规则中引用这些变量。 语法格式:VAR_NAME = value 取消回显:由于调用make命令,其会默认显示回显,因此一般通过使用 @ 加在命令前面取消回显 条件判断:可以使用条件判断(ifeq、ifdef 等)来根据不同的条件执行不同的命令。 函数:Makefile 支持一些内置函数,可以用于字符串处理、文件查找等操作。
使用make和make clean,就可以方便地完成项目自动化构建和清理。
注意:make 默认只生成一个可执行程序
make命令不是每次都会重新编译,只有更改过的文件才会重新编译。(提高编译效率) 若源代码没有更改也重新编译,那么每次预处理编译汇编链接的时间比较长,成本高
make/Makefile是如何知道文件更改过的? 答:通过源文件的修改时间和形成的可执行程序(也是文件)的修改时间做对比。
重新编译的本质:重新写入一个二进制的可执行文件(bin文件),文件的修改时间会跟着更改。
大部分情况下重新编译都没问题,问题的产生不仅仅是修改新文件就能解决的。有些历史问题需要重新清理项目才可以解决。
文件 = 内容 + 属性,所以文件的ACM时间肯定与内容或属性有关。 Access(最近访问时间):普通文本文件打开:cat、vim,或者对目录进入、ls显示等 Modify (对内容修改):当文件内容发生变化时,修改时间(mtime)会被更新。 Change(对属性修改):当文件的权限、所有者、链接数或文件名甚至文件大小发生变化时,更改时间(ctime)会被更新。 注:
综上,我们知道 make 是通过对比源文件和bin文件的Modify时间确定文件新旧的。
.PHONY 配置项的目标clean并不是其他文件生成的实际文件,使make命令会自动绕过隐含规则搜索过程,也就是说执行命令make clean会自动忽略名为"clean"文件的存在,因此声明 .PHONY 配置项会改善性能,并且不需要担心实际同名文件存在与否😮。 【通俗一点说】:.PHONY 修饰的目标clean并不是某个依赖项生成的实际文件,因此make命令不再去搜寻当前文件夹下是否有clean文件,这样少去做一些事,自然会改善性能,并且不用担心当前文件夹下是否有同名的文件。
通过时间对比,可以做到不让有些代码进行重新编译(不让某些操作进行)。
如上:右边的test被.PHONY修饰,则多次make时,都会执行gcc命令,把可执行程序重新形成。
💢 越是接近目标文件的命令,就越是要写在前面。因为程序是按照递归的方式进行依赖文件查找的,看到第一行有一个没见过的依赖文件,就往下一行进行查找,以此类推。
对于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
但是一般都不会写成这么复杂,一般都是如下写法:
符号 | 含义 |
---|---|
= | 替换 |
%.o | 任意的.o文件 |
%.c | 任意的.c文件 |
符号 | 含义 |
---|---|
$^ | 所有依赖文件 |
$@ | 所有目标文件 |
$< | 所有依赖文件的第一个文件 |
此外:Makefile中可以编写变量,表达式之间不建议带空格 通过 $(变量名) 来引用变量的值。
@ 和 ^,前者表示:左侧被编译的所有内容,即【目标文件】,后者表示:之后所有内容,即【依赖文件】。
此时,当我们再去make的时候,就可以发现这个特殊符号自动替换成了:两侧的【目标文件】和【依赖文件】。
一般来说make 默认只生成一个可执行程序如下:
但是我们可以做出以下修改:
一般来说,对于一个文件要生成其对应的可执行程序,我们一般这样操作:
// 生成 code.c 可执行程序
code: code.c
gcc -o $@ $^
.PHONY: clean
clean:
rm -f code
以上就是Make 和 Makefile的全部内容啦,后面我们就要讲到进度条的相关内容,就会运用到这一节的知识,敬请期待咯!!! 💞 💞 💞那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心