一、多个源文件带来的问题
在编写小程序时,许多人都会在编辑完源文件后简单地重新编译所有文件以重建应用程序。但对大型程序来说,
使用这种简单到处理方式会带来很明显的问题。编辑-编译-测试这一循环的周期将变长。如果仅改动了一个源文件,
即使是最有耐心到程序员也不想重新编译所有到源文件。
如果在程序中创建了多个头文件,并在不同到源文件中包含它们,就会带来潜在的、更严重的问题。比如说,
我们有三个头文件:a.h、b.h和c.h,三个C语言源文件main.c、2.c和3.c,具体情况如下所示:
- #include "a.h"
- ...
- /* 2.c */
- #include "a.h"
- #include "b.h"
- ...
- /* 3.c */
- #include "b.h"
- #include "c.h"
- ...
如果程序员只修改了文件c.h,则源文件main.c和2.c无需重新编译,因为它们并不依赖于这个头文件,而对源文件3.c来说,
因为它包含了头文件c.h,所以在文件c.h改动后,就必须重新编译它。但如果修改的是文件b.h,而程序员又忘记重新编译源文件2.c,
则最终的程序就可能无法正常工作了。make工具可以解决上述这些问题,它会在必要时重新编译所有受改动影响的源文件。
虽然make命令内置了许多智能机制,但光凭其自身是无法了解应该如何建立应用程序的。用户必须为其提供一个文件,
告诉它应用程序应该如何构造,这个文件成为makefile。
makefile文件一般都会和项目到其它源文件放在同一个目录下。你的机器上可以同时存在许多不同到makefile文件。事实上,
如果管理的是一个大项目,你可以用多个不同的makefile文件来分别管理项目的不同部分。
make命令和makefile文件的结合提供了一个在项目管理领域的十分强大的工具。它不仅常被用于控制源代码的编译,还用于使用手册的编译及将应用程序安装到目标目录。
二、makefile文件的语法
makefile文件由一组依赖关系和规则构成。每个依赖关系由一个目标(即将要创建的文件)和一组该目标所依赖的源文件组成。
而规则描述了如何通过这些依赖文件创建目标。一般来说,目标是一个单独的可执行文件。
make命令会读取makefile文件的内容,它先确定要创建的目标文件,然后比较该目标所依赖的源文件到日期和时间,
以决定该采用哪条规则来构造目标。通常在创建最终的目标文件之前,需要先创建一些中间目标。make命令会根据makefile
文件来确定目标文件的创建顺序以及正确的规则调用顺序。
1)make命令的选项和参数
make命令本身有许多选项,其中最常用的三个选项是:
- -k:它的作用是让make命令在发现错误时仍然继续执行,而不是在检测到第一个错误时就停下来。我们利用这个选项在一次操作中发现为编译成功的源文件。
- -n:它的作用是让make命令输出将要执行的操作步骤,而不真正执行这些操作。
- -f filename:它的作用是告诉make命令将哪个文件作为makefile文件。如果未使用这个选项,make命令将首先查找当前目录下名为makefile的文件,如果该文件不存在,就会查找名为Makefile的文件。
1. 依赖关系
myapp: main.o 2.o 3.o
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
all:myapp myapp.l
2. 规则
myapp: main.o 2.o 3.o gcc -o myapp main.o 2.o 3.o main.o: main.c a.h gcc -c main.c 2.o: 2.c a.h b.h gcc -c 2.c 3.o: 3.c b.h c.h gcc -c 3.c
[root@localhost ~]# make -f Makefile1
make: *** 没有规则可以创建“main.o”需要的目标“main.c”。 停止。
[root@localhost ~]# touch a.h
[root@localhost ~]# touch b.h
[root@localhost ~]# touch c.h
文件main.c中包含main函数,该函数调用了function_two和function_three函数,而这两个函数分别在另外两个文件中定义。源文件通过#include语句包含合适的头文件,
使它们看上去依赖于这些头文件的内容。下面是其程序的清单:
/* main.c */
#include <stdlib.h>
#include "a.h"
extern void function_two();
extern void function_three();
int main()
{
function_two();
function_three();
exit(EXIT_SUCCESS);
}
/* 2.c */
#include <stdlib.h>
#include "a.h"
#include "b.h"
void function_two()
{
printf("function_two\n");
}
/* 3.c */
#include <stdlib.h>
#include "b.h"
#include "c.h"
void function_three()
{
printf("function_three\n");
}
再次执行make命令:
[root@localhost linux]# make -f Makefile1
gcc -c main.c
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
这次成功执行了make。
实验解析
make命令处理makefile文件中的依赖关系,确定需要创建的文件以及创建顺序。虽然我们把目标myapp列在最前面,
但make命令能够自行判断出创建文件的正确顺序。它调用在规则部分给出的命令以创建相应的文件,同时会在执行时在
屏幕上将命令显示出来。现在,我们测试在文件b.h改变时,makefile文件能否正确处理这一情况。
[root@localhost linux]# touch a.h
[root@localhost linux]# make -f Makefile1
gcc -c main.c
gcc -c 2.c
gcc -o myapp main.o 2.o 3.o
make命令读取makefile文件,确定重建myapp所需的最少命令,并以正确的顺序执行它们。下面我们来看看删除一个目标文件后的情况:
[root@localhost linux]# rm 2.o
rm:是否删除普通文件 "2.o"?y
[root@localhost linux]# make -f Makefile1
gcc -c 2.c
gcc -o myapp main.o 2.o 3.o
make命令再次正确地确定出需要采取的动作。