makefile文件详细解析

#什么是makefile

Most often, the makefile directs Make on how to compile and link a program.

在软件开发中,makefile是一类非常重要的文件,是程序员实现自动化编译程序的一种手段。
#从例子中学习
我们现在在一个文件夹放置着sort0.h,sort.c,sortMain.c三个文件。其中sortMain.c和sort0.c中包含了sort.h。
在linux环境下,如果想进行编译链接,生成可执行文件,需要输入下面的命令。
gcc -o sortMain sort0.c sortMain.c
如果没有makefile,我们在开发和调试的过程中需要不停地输入命令,这样做有很多的不足。一是会做许多重复的劳动,浪费时间,效率比较低下。二是如果工程比较大,文件很多的时候,文件之间的依赖关系会非常复杂,在编译和链接时极容易出错。三是一旦工程中修改了一个文件,就会导致整个工程被重新编译,非常影响开发效率。由于以上种种不足,我们需要用makefile来解决。
上面一行命令在makefile中该如何表示呢。

1
2
sortMain : sort0.c sortMain.c
gcc -o sortMain sort0.c sortMain.c

这就是一个最基本的makefile文件了,它主要由三部分组成。

1
2
target : prerequisites...
command

第一部分是target,这里是sortMain,表示我们想要生成的目标文件。
第二部分是prerequisites,这里是sort0.c sortMain.c,这里是依赖表,表示想要生成目标文件,必须提供表中的文件。
第三部分是command,是一条命令。
一二部分叫做依赖关系表,二中的文件只要有一个发生了变化,就会触发第三部分command。
有了makefile,我们只要把它放在工程的目录下,输入make就可自动执行。

请注意,在第二行的“gcc”命令之前必须要有一个tab缩进。语法规定Makefile中的任何命令之前都必须要有一个tab缩进,否则make就会报错。

#改进makefile
上面的makefile只有最基本的功能,我们接下来一步步修改它,实现更加灵活的功能。
先加入几个容器

1
2
3
4
5
6
1 cc = gcc
2 prom = sortMain
3 src = sort0.c sortMain.c
4
5 $(prom) : $(src)
6 $(cc) -o $(prom) $(src)

加入的几个变量严格意义上是不变的,类似于c语言中的宏定义。把cc,prom,src理解成容器,把对应的内容放进去,可以随时修改。在写makefile每一组成单元的时候,可以使用这些容器,避免了重复的字符串输入。也方便后续修改。
现在我们的makefile比较灵活了,但是仔细观察,发现我们的依赖关系仍然很重,无法解决修改一个文件需全部重新编译的问题。
我们发现整理出一个合适的依赖关系链非常有必要,而关系链也不是唯一的,根据工程实际考虑。在这个例子中,因为sort.h不在依赖关系表中,所以它的变化不会被侦测到,需要把其引入。我们考虑编译的具体过程,可执行文件生成需要依赖.o文件,.o文件的生成分别依赖各自对应的.c文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
 1 cc = gcc
2 prom = sortMain
3 deps = sort.h
4 obj = sort0.o sortMain.o
5
6 $(prom) : $(obj)
7 $(cc) -o $(prom) $(obj)
8
9 sort0.o : sort0.c $(deps)
10 $(cc) -c sort0.c
11
12 sortMain.o : sortMain.c $(deps)
13 $(cc) -c sortMain.c

以上,我们可以实现依赖中包含所有源文件,重新部分编译等功能。但是,我们发现代码非常冗余,需要利用makefile的语法规则精简以上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
 1 cc = gcc
2 prom = sortMain
3 deps = sort.h
4 obj = sort0.o sortMain.o
5
6 $(prom) : $(obj)
7 $(cc) -o $(prom) $(obj)
8
9 %.o : %.c $(deps)
10 $(cc) -c $< -o $@
11
12 clean:
13 rm $(obj) $(prom)

这样就简洁多了,但是好像还缺少一个功能,就是我们经常使用的make clean。我们把它加上。
接下来我们还要解决一个问题,就是向工程中添加.c.h文件的时候需要手工操作,为此进一步修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 1 cc = gcc
2 prom = sortMain
3 deps = $(shell find ./ -name "*.h")
4 src = $(shell find ./ -name "*.c")
5 obj = $(src:%.c=%.o)
6
7 $(prom) : $(obj)
8 $(cc) -o $(prom) $(obj)
9
10 %.o : %.c $(deps)
11 $(cc) -c $< -o $@
12
13 clean:
14 rm $(obj) $(prom)

主要是添加了shell函数,这里是发现当前路径内所有的.h.c文件。再把src中所有*.c替换成*.o,实际上就是列出了所有.c文件(需要编译成.o文件)。


总结一下,从makefile的详细制作步骤中,我们可以学习到优化的过程,从中建立一种思想:对于扩展的需求,只需要去查找相应的规则用以进行编写,实现一次编码,多次使用,提升开发的效率。