Linux基础学习笔记十一:Makefile依赖关系理解
一、前言
最近在看makefile
,各文件之间的依赖关系整的我有点懵,看了好久还是迷迷糊糊,可能是我看的资料比较少。反正下面就通过一个例子逐渐引出自己的理解,完全是自己瞎琢磨,有错误欢迎指正!!!
一个简单的例子:4个.c
文件,3个.h
文件,其中mytest调用了test1,test2,test3;test1调用了test2,test3;test2调用了test3。
4个.c文件如下:
3个.h文件如下:
二、Makefile写法
1、最常规的写法:
mytest: mytest.o test1.o test2.o test3.o
gcc -o mytest mytest.o test1.o test2.o test3.o
mytest.o: mytest.c mytest.h test1.h test2.h test3.h
gcc -c mytest.c
test1.o: test1.c test1.h test2.h test3.h
gcc -c test1.c
test2.o: test2.c test2.h test3.h
gcc -c test2.c
test3.o: test3.c test3.h
gcc -c test3.c
.PHONY:clean
clean:
-rm -rf *.o mytest
这种写法没有可说的,就是把每个目标所需的依赖都给写出来了,包括.h
依赖文件。
2、无.h
依赖文件的写法:
mytest: mytest.o test1.o test2.o test3.o
gcc -o mytest mytest.o test1.o test2.o test3.o
mytest.o: mytest.c
gcc -c mytest.c
test1.o: test1.c
gcc -c test1.c
test2.o: test2.c
gcc -c test2.c
test3.o: test3.c
gcc -c test3.c
这种写法与第一种写法的区别就在于没有为每个目标添加.h
依赖文件。
3、自动推导(简化)版本:
mytest: mytest.o test1.o test2.o test3.o
gcc -o mytest mytest.o test1.o test2.o test3.o
mytest.o: mytest.c
gcc -c mytest.c
test1.o: test1.c
test2.o:
gcc -c test2.c
test3.o:
由以上可知,可以省略.c
依赖文件、指令,或者两者都省略。
由下面终极自动推导的例子可以看出,上面四句甚至可以直接不写!!!
mytest: mytest.o test1.o test2.o test3.o
gcc -o mytest mytest.o test1.o test2.o test3.o
三、问题分析
1、没有.h
依赖文件为什么会编译成功?
我本来以为在第一种写法中,.h
依赖文件刚好作为连接各个文件的桥梁,源文件中使用的各种变量或者函数才能被正确找到,这样整个工程才能编译成功。可是现在看来,没有.h
依赖文件也可以编译成功。为什么呢?
我个人的理解是,在编译时,以mytest.c
源文件为例,其中有如下头文件包含语句:
#include "test1.h"
#include "test2.h"
#include "test3.h"
这些头文件包含语句,就已经可以建立起各个文件之间的调用关系了,不用在makefile
中指明就可以编译成功了,注意这里说的仅仅是编译成功!!!
2、自动推导版本中,修改某个文件,再次make,哪些目标会被重新生成?
- 先修改
test3.c
源文件进行测试:
由上图可以看到,第一次make
时,一共执行了五条指令,这是因为一开始这些目标都不存在,所以需要生成这5个目标文件。
这时候,我修改了test3.c
源文件,在该文件最后加了一句:
printf("modified\n");
此时再次make
(注意,在此之前,没有make clean
),从下图可以看到这次只执行了两条指令,也很好理解,因为test3.c
被修改了,所以test3.o
要重新生成,则mytest
也要重新生成。
从上面的运行结果可以看出,虽然只重新生成了test3.o
,而test1.o和test2.o、mytest.o
都没有重新生成,但是最后所有源文件的执行结果都多了添加的modified
,也即test3.c
的修改对它们都生效了。这是为什么呢?明明只重新生成了test3.o
,然后重新生成应用程序mytest
,其他文件没有被修改也没有被重新生成。
我个人理解是,比如test1.c
调用了test3.c
中的函数,其实并不是它把被调用函数的代码复制过来了(这种情况下,修改了test3.c
,test1.c
的功能不会受影响),而是可以认为它获取了被调用函数的指针,这样在执行到调用的函数时,调用的仍然是修改后的函数!!!
- 再修改
test3.h
头文件进行测试:
可以看到,此时显示mytest
为最新,没有重新生成任何目标文件。
之所以发生这种情况,是因为Makefile
的自动推导功能只会推导出目标文件对源文件的依赖关系(所以,自动推导版本的makefile
补全后,其实就是无.h
依赖文件的写法),而不会在依赖关系中添加头文件!这导致的直接问题就是:当第一次执行make
后,再次修改依赖的.h
头文件的内容,自动推导功能只会去检测.c
文件的修改时间戳,发现没有变化则不会再次编译生成.o文件,进一步导致不会进行重新make
链接生成mytest
目标文件(因为检测到4个.o
文件都没有变化)!!!除非修改头文件后运行一次make clean
,再运行make
。
四、小结
1、makefile
中的依赖关系,不是为了使编译通过,更多的是为了实现当修改某个文件时,与其相关的文件也能被重新编译,避免出现修改无效的情况!!!
2、自动推导只会推导出目标文件对源文件的依赖关系,而不会在依赖关系中添加头文件!!!
因此,如果你在项目中只涉及到修改源文件,那么可以不用添加头文件依赖;但是如果你还需要修改.h
头文件,那么在makefile
中一定要将.h
依赖文件根据实际情况添加上!!!