Makefile 目标依赖
make第一次编译某个项目时,会依次编译所有的源文件。但是当我们修改程序后,再次使用make编译,make并不会重新编译整个源文件,而是只编译你新添加或修改了的源文件,那make是如何做到的呢?
很简单,make是根据时间戳来判断一个规则中的目标依赖文件是否有更新。make在编译程序时,会依次检查依赖关系树中的所有源文件的时间戳,如果发现某个文件的时间戳有更新,会认为这个文件有改动过,会重新编译这个源文件。如果发现文件的时间戳没有更新,就不会再重新编译一次。
但是还有一种情况我们没有考虑到:在Makefile的规则中,一般不会把头文件添加到目标依赖中。当一个.c文件中包含多个头文件时,如果对应的头文件发生了变化,因为头文件没有包含在依赖关系树中,所以这个.c文件就不会重新编译。如下面的程序:
//hello.c
#include <stdio.h>
#include "module.h"
int main(void)
{
printf("PI = %f\n", PI);
func();
printf("hello zhaixue.cc!\n");
return 0;
}
//module.c
#include <stdio.h>
void func(void)
{
printf("hello func!\n");
}
//module.h
#ifndef __MODULE_H__
#define __MODULE_H__
#define PI 3.14
#endif
//Makefile
.PHONY: clean
a.out: hello.o module.o
gcc -o a.out hello.o module.o
hello.o: hello.c
gcc -c -o hello.o hello.c
module.o: module.c
gcc -c -o module.o module.c
clean:
rm -f a.out hello.o
第一次使用make编译生成a.out运行无误后,然后修改module.h中的宏定义PI值为3.1415,再次使用make编译程序,你会发现make并没有重新编译:
wit@pc:/home/makefile# make
make: 'a.out' is up to date.
这是因为module.h并没有添加到Makefile的规则依赖目标中,所以无论你怎么修改module.h,都不会重新编译helloworld.c源文件。
头文件依赖
其中一个解决方法是将头文件module.h添加到规则的目标依赖列表中:
//Makefile
.PHONY: clean
a.out: hello.o module.o module.h
gcc -o a.out hello.o module.o
将头文件添加到规则的目标依赖中,虽然能解决依赖关系的问题,但是也有弊端,当helloworld.c包含几十个头文件时,把包含的这些头文件都手工添加进去,工作量还是蛮大的。如果一个项目中有成百上千个C文件,每个C文件包含几十个头文件,按照这种手工添加的方法,996无疑。
自动生成头文件依赖关系
一个更高效的解决方法是:使用gcc -M 命令自动生成头文件依赖关系
wit@pc:/home/makefile# gcc -M hello.c
hello.o: hello.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/9/include/stddef.h \
/usr/lib/gcc/x86_64-linux-gnu/9/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/include/x86_64-linux-gnu/bits/timesize.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h \
/usr/include/x86_64-linux-gnu/bits/time64.h \
/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \
/usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
/usr/include/x86_64-linux-gnu/bits/types/FILE.h \
/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h module.h
如上所示,hello.c源文件中包含了两个头文件:stdio.h和module.h,通过gcc -M命令,我们就可以自动生成一个hello.o目标文件的依赖关系,就不需要我们手动将头文件添加到规则中了。
至于在Makefile中如何使用gcc -M命令自动生成头文件依赖关系,如何使用。这个还需要进一步的解析和处理,涉及到正则表达式、模式匹配等,有点小复杂,一句话两句话说不清楚,如有兴趣,可观看Makefile进阶视频教程学习:Makefile工程实践第01季:从零开始一步一步写项目的Makefile。