在大型工程中,源文件众多,通过合理定义makefile可以规范文件的编译顺序与重新编译规则。本文将深入探讨makefile的基本概念、命名规则、变量和函数的运用,以及通过示例演示makefile的不同层次的优化。
在工程中,makefile定义了源文件的编译规则和操作系统命令。它不仅指定文件的编译顺序,还可以执行操作系统的命令,是完成大型工程的必备工具。因此,一个人是否能熟练编写makefile,反映了其完成大型工程的能力。
makefile的命名只有两种形式:makefile
和Makefile
,其他形式不被允许。
Makefile中的规则由三个基本要素组成:目标(target)、依赖(prerequisites)和命令(command)。以下是其内部型编写形式:
target: prerequisites
command
**目标(target)**代表着一个文件或标签,可以是Object File,也可以是执行文件。对于标签的特性,将在后续“伪目标”章节中详细叙述。
**依赖(prerequisites)**指定了生成目标文件(target)所需要的文件或目标。这是一个文件的依赖关系,即目标文件依赖于prerequisites中的文件。
**命令(command)**包含了make需要执行的任意Shell命令。这一部分定义了生成目标文件的具体规则。如果prerequisites中的文件比目标文件新,make将执行所定义的命令。
这些规则构成了Makefile中最为核心、关键的内容,规定了文件生成的逻辑和依赖关系。
makefile中使用变量和函数有助于提高代码的灵活性和可维护性。
** 变量 **
(1) 规则
%o : %c
Tab键 gcc -c $< -o $@
(2) makefile中的自动变量
(a) $< : 规则中的第一个依赖
(b) $@ : 规则中的目标
(c) $^ :规则中的所有依赖
(3) makefile中系统自己维护的变量
(a) CC = cc (其实 cc 是系统默认的gcc ,当然 CC 的值可以赋值 )
(b) CPPFLAGS = -I ( 编译时需要的参数 )
** 函数 **
(1) wildcard
src = $(wildcard /*.c) 表示把当前目录下的所有.c文件返回一个字符串给src
(2) patsubst
obj = $(patsubst ./*.c , ./*.o , $(src)) 表示把当前目录下src的所有.c文件换成.o文件
通过初级、中级、高级和终极的makefile示例演示了不同级别的优化,包括减少冗余命令、根据依赖项精确编译等。
calc:calc.c add.c sub.c mul.c div.c
gcc calc.c add.c sub.c mul.c div.c -o calc
通过写 makefile 减少了 编译运行程序时,繁杂冗余的命令出现
calc:calc.o add.o sub.o mul.o div.o
gcc calc.o add.o sub.c mul.o div.o -o calc
calc.o:calc.c
gcc -c calc.c
add.o:add.c
gcc -c add.c
sub.o:sub.c
gcc -c sub.c
mul.o:mul.c
gcc -c mul.c
div.o:div.c
gcc -c div.c
clean:
rm *.o calc
中级makefile 比初级makefile 更好一点,主要是因为: 初级的makefile在任何时候,只要其中的一个依赖文件改变,就必须重新编译,这样会导致大量时间被浪费。
如果所依赖的文件比较多的话,那么编译就会耗费大量时间。而中级makefile 会根据哪一个依赖项 发生改变,则这个目标文件重新编译,不用全部重新编译 。
obj = calc.o add.o sub.o mul.o div.o
target = calc
# makefile中的自动变量
CC = gcc
CPPFLAGS = -I
$(target):$(obj)
$(CC) $(obj) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
高级makefile 的性能就更好了,而且比较标准和规范化,用到了变量和公式,避免了makefile中出现大量的相同代码。
#obj = calc.o add.o sub.o mul.o div.o
target = calc
# makefile中的自动变量
# makefile中的函数使用
# src 是获取当前目录下所有.c文件
src = $(wildcard ./*.c)
# obj 把当前目录下所有的.c替换成.o
obj = $(patsubst %.c, %.o, $(src))
CC = gcc
CPPFLAGS = -I
$(target):$(obj)
$(CC) $(obj) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
# .PHONY:clean 意思是申明clean 为伪目标,它不会和磁盘上的 clean 进行比较
.PHONY:clean
clean:
rm $(obj) $(target) -f
hello:
echo "Hello,makefile"
以一个具体工程为例,展示了包含多个头文件和C文件的makefile,通过变量、规则和自动推导等手段简化了代码结构,提高了可维护性。
# 定义目标文件
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
# 默认目标为edit,生成执行文件
edit : $(objects)
cc -o edit $(objects)
# 隐晦规则,make会自动推导依赖关系和生成规则
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
# 伪目标,用于清理生成的文件
.PHONY : clean
clean :
rm edit $(objects)
变量的使用 (Variable Usage):
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
: 定义了一个变量 objects
,其中包含了所有的目标文件。这样的定义使得代码更加清晰,同时方便后续的修改,只需要在这个变量中添加或删除目标文件即可。
edit : $(objects)
: 在生成目标文件 edit
时,使用了变量 $(objects)
,这样做使得代码更简洁,并且方便维护。
隐晦规则 (Implicit Rules):
针对每个 .o
文件,只需提供对应的头文件,make 会自动推导生成规则。例如,main.o : defs.h
表示 main.o
依赖于 defs.h
,而不需要手动指定生成规则。
这种方式减少了代码的冗余,提高了可读性,并使代码更加易于维护。
伪目标 (Phony Target):
.PHONY : clean
表示 clean
是一个伪目标。伪目标通常用于表示执行一些操作而不生成实际文件,例如清理操作。
clean : rm edit $(objects)
定义了清理操作,可以通过执行 make clean
来删除生成的执行文件和目标文件。
这样的 Makefile 更加简洁、可读性更强,并且降低了维护的难度。通过使用变量和隐晦规则,可以更方便地扩展项目,而使用伪目标则使清理操作更加安全和可控。
本文解释了make的工作原理,包括寻找makefile、目标与依赖关系的比较、执行命令等步骤。希望大家对make的运行机制有更深刻的理解,可以更好地理解makefile的规范与优化方法。合理的makefile不仅提高了代码的编译效率,还使得工程更易于维护。在实际项目中,充分利用makefile的功能,将对项目的开发和维护产生积极的影响。