一、关于make和makefile的基本概念和使用:
Make:
是一个能自动判断一个大型程序中哪些源代码需要重新编译 的工具
并且根据判断的结果自动调用编译器去编译源代码
最终按照一定的顺序,将编译结果整合成可执行程序
当项目中的某个文件做过修改,没有的make的情况下可能要重新编译整个项目,而make可以自动的判断哪些需要重新编译,哪些需要先进行编译,节省大量时间。
makefile:
可以认为是一个特殊的脚本文件,对于大型的项目,有很多的源文件需要编译,我们可以通过编写makefile来实现自动的编译,就不需要我们挨个去编译…
makefile来制定的规则,而make来执行这一些规则。
对于源文件比较少的时候:可以我们手动gcc,但是当文件数量特别大时就很不现实
gcc a.c b.c c.c d.c ..... -o main
//传统编译链接 如果有很多源文件,看起来很繁琐
一般makefile包含的内容:
1.文件的结构:对什么样的文件进行操作,文件间的引用关系
2.具体的指令:对这些文件怎么操作
makefile早期的基本语法:
main.o:main.c median.h
gcc- c main.c
//目标文件名字:生成目标文件的 依赖的 其他源文件和头文件
//编译规则:Tab键不能少,不能用空格代替 然后将规则输入,main.o是根据这个规则生成的
因为make判断这行是不是命令就是根据这里有没有tab键来判断
这样有个缺点,如果文件过多,还是需要我们统统输入文件名,甚至有时候不知道源文件包含了
哪些文件,感觉很繁琐。
如上图:
解析makefile的基本工作流程:
1.make命名从上往下读取makefile文件时,make命令第一个遇到的文件名是最终要生成的文件,上图中是 all
2.
先看all生成没有,如果没有去看all的依赖top有没有,
如果top没有,再去看top的依赖main.o sort.o median.o有没有生成
假如发现main.o sort.o median.o都没有生成,会根据这三个文件的生成规则依次的去生成这三个文件
(我感觉make命令执行的路径像是一个递归的过程,或者说这本身就是个树形结构)
等到这三个文件生成,再去根据top的生成规则去生成top
然后最后生成all
make命令第一次执行 :
文件最终生成/修改时间先后顺序为:
main.o—>sort.o—>median.o------>top----->all
早 ——————————————> 晚
因为只有当目标文件 所依赖的 依赖文件 生成才能去生成目标文件。
make命令再次执行:
make命令同样的会从上到下的检查所有文件时间戳
如果发现某个目标文件的依赖文件的时间戳居然比最终目标文件all还要迟
就说明这个**文件就被修改过,**依赖这个文件的目标文件就需要重新的编译
(其他不依赖该文件的目标文件不需要重新编译)
比如说:
median.o文件所依赖的median.h的时间戳比all的时间戳居然晚,
说明median.o在前一次的编译以后被修改过,所以median.o和main.o将会被make命令重新编译
最后与其他目标源文件sort.o链接,重新生成新的最终目标文件。
注:
Linux上我们ls -l就可以查看到文件的最终修改时间。
make是通过文件的时间戳以及文件之间的相互依赖关系来决定哪些文件需要重新编译
二、make的隐含规则
1.make知道如何用什么样的工具编译这些文件,如c语言用gcc,c++用g++
2.
foo:foo.o bar.o
gcc -o foo.o bar.o
//make认为foo.o和bar.o分别来自于foo.c和bar.c,所以我们就不用去写
foo.o:foo.c
gcc -c foo.c
类似的内容,make会默认的去寻找foo.c去编译它,前提是命名要有规律~
比如main.o它认为就是main.c编译来的
所以,对于上面图片中的makefile的内容直接简化成:
all:top
top:main.o sort.o median.o
gcc main.o sort.o median.o -o top
CC = gcc
AR
AS
....... 具体参考makefile的隐含规则
三、模式规则
用户自己定义新的隐含规则
格式:
模式规则和普通的规则相似
模式规则的目标有且仅有一个"%","%"可以匹配任意非空的字符串
( % 就像是 linux下的通配符 * 的含义)
’ %.o:%c’
表示把任意一个.c文件编译成.o文件
自动变量:
$@ 表示规则中的 目标文件 的名字
$< 表示规则中源文件 依赖文件 的名字
$^ 表示所有 依赖文件 的名字
四、自动搜寻源文件所依赖的头文件
存在的问题:
一个源文件可能依赖好多头文件,这些头文件还有可能依赖其他的好多的头文件
编译器的支持:
现代的编译器可以 提供源文件依赖的所有头文件
比如编译c源文件的时候:
gcc -M main.c
-M:该选项会将生成main.o所依赖的所有头文件输出到.d文件
对于每个源文件,xxx.c编译器都会产生一个xxx.d的文件
这个 .d 文件中保存着xxx.o所依赖的所有头文件
我在我的ubuntu中写一个main.c,然后gcc -M main.c
king@ubuntu:~/mypython$ gcc -M main.c
main.o: main.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/bits/long-double.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
.....
我在这里并没有列出所有的头文件
我们可以在makefile中将.d文件包含进去
include$(source :.c = .d)