不知道各位小伙伴是否碰到过这样的情况:
一个 .c
文件 include
另一个 .h
头文件,使用 Makefile
来构建(编译)应用程序。
第一次编译、执行,很正常!
但是此时,如果修改了 .h
头文件,再次编译时,就出现问题了:
预期的执行流程是:make
发现 .h
头文件的修改时间更新,于是重新编译包含这个头文件的所有 .c
文件。
可实际的结果却是:make
并没有识别出 .h
头文件的修改。
这是怎么回事呢?让我们一一道来。
简单的代码示例
一个头文件:hello.h
#ifndef _HELLO_
#define _HELLO_
#define NUM 1
#endif
一个源文件:main.c
#include <stdio.h>
#include "hello.h"
int main(int argc, char *agv[])
{
printf("NUM = %d n", NUM);
return 0;
}
Makefile
文件:
OBJS := main.o
TARGET := main
all : $(OBJS)
gcc -o $(TARGET) $(OBJS)
%.o: %.c
gcc $< -c -o $@
现在我们来第一次执行 make
,编译一下:
$ make
gcc main.c -c -o main.o
gcc -o main main.o
执行一下:
代码语言:javascript复制$ ./main
NUM = 1
我们现在把 hello.h
文件中的 NUM
改成 2
,现在的文件修改时间是:
$ ll
total 28
-rw-rw-r-- 1 root root 58 Jun 7 20:52 hello.h
-rwxrwxr-x 1 root root 8608 Jun 7 20:51 main*
-rw-rw-r-- 1 root root 122 Jun 7 20:51 main.c
-rw-rw-r-- 1 root root 1528 Jun 7 20:51 main.o
-rw-rw-r-- 1 root root 100 Jun 7 20:51 Makefile
然后再执行 make
指令,编译一下:
$ make
gcc -o main main.o
可以看到:make
只执行了 Makefile
中的链接指令(从目标文件 main.o
到可执行文件 main
),并没有执行 gcc main.c -c -o main.o
这条编译指令来重新编译目标文件。
也就说明:make
并没有识别出 hello.h
这个头文件已经被改动了,尽管它“应该”可以从文件的修改时间上发现!
为什么会这样?
我们来看一下 Makefile
中的这个规则:
%.o: %.c
gcc $< -c -o $@
目标文件 main.o
,只是依赖了 main.c
文件,并没有依赖 hello.h
文件。
make
的执行规则是:只有目标文件不存在,或者依赖文件比目标文件更新的时候,才会执行编译指令。
因此,虽然 hello.h
被修改了,但是它并不是目标文件 main.o
的依赖。
make
发现:main.o
在当前目录中是已经存在的,并且它比 main.c
更新,因此不会重新编译 main.o。
所以即使 hello.h
被修改了,也不会起作用,因为 make
压根就不把 hello.h
当做 main.o
的依赖!
注意:所有的操作过程没有执行 clean 操作。
最简单、无脑的方法
既然知道了原因,那就好办了,我们手动把头文件 hello.h
加到依赖中,不就可以了吗?!
把 Makefile
中最后面几句修改成下面这样:
HEADERS := hello.h
%.o: %.c ${HEADERS}
gcc $< -c -o $@
也就是把 .h
文件,也加入到 .o
文件的依赖中,这样的话,每次修改 .h
文件后,再执行 make
指令时,就可以重新编译 .o
目标文件了。
您可试一下,这样做肯定是没有问题的。
到此,问题是被解决了,但是总觉得这样的方式比较粗鲁。
想一下:如果有很多的 .c
和 .h
文件呢,总不能手动一个一个添加吧?
高级一点的方法
修改 Makefile
为下面这样:
OBJS := main.o
TARGET := main
all : $(OBJS)
gcc -o $(TARGET) $(OBJS)
-include *.d
%.o: %.c
gcc $< -c -MMD -o $@
改动部分有 2 处:
1. 添加了 -include *.d 指令; 2. gcc 编译指令中,添加了 -MMD 参数;
我们先执行一下试试。第一次编译:
代码语言:javascript复制$ ll // 查看当前文件
total 12
-rw-rw-r-- 1 root root 58 Jun 7 21:06 hello.h
-rw-rw-r-- 1 root root 122 Jun 7 20:51 main.c
-rw-rw-r-- 1 root root 119 Jun 7 21:05 Makefile
$
$ make // 编译
gcc main.c -c -MMD -o main.o
gcc -o main main.o
$
$ ll // 再次查看当前文件
total 32
-rw-rw-r-- 1 root root 58 Jun 7 21:06 hello.h
-rwxrwxr-x 1 root root 8608 Jun 7 21:06 main*
-rw-rw-r-- 1 root root 122 Jun 7 20:51 main.c
-rw-rw-r-- 1 root root 23 Jun 7 21:06 main.d
-rw-rw-r-- 1 root root 1528 Jun 7 21:06 main.o
-rw-rw-r-- 1 root root 119 Jun 7 21:05 Makefile
$
$ ./main // 执行
NUM = 1
有没发现:多出了一个文件 main.d,该文件内容是:
代码语言:javascript复制main.o: main.c hello.h
这个文件正是因为 Makefile
中的 -MMD
这个参数导致生成的,而它的内容正是我们需要的目标文件依赖信息。
然后在 Makefile
中,include
这个 .d
文件,从而让 make
知道:main.o 文件依赖于 main.c 和 hello.o 这 2 个文件。
这个时候,我们再来修改 hello.h
中的内容,例如:把 NUM
改成 10
,再次编译、执行:
$ make
gcc main.c -c -MMD -o main.o
gcc -o main main.o
$
$ ./main
NUM = 10
Bingo,结果正确!
------ End ------