

在Linux开发环境中,我们经常需要编译和构建复杂的项目。手动一个个编译源文件不仅效率低下,而且容易出错。这时候,make和Makefile就成为了每个Linux开发者必须掌握的利器。本文将带你从零开始,深入理解make和Makefile的工作原理,并通过实战案例展示如何编写高效的自动化构建脚本。
简单来说,make是一条命令,Makefile是一个文件,两个搭配使用,完成项目自动化构建。
详解:
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
makefile带来的好处就是⸺“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
make是一个命令工具,是一个解释makefile中指令的命令工具。
首先,建立一个Makefile文件
touch Makefile # Makefile/makefile都可以,我们习惯于Makefile然后,保证你有个mypro.c的c文件,然后对Makefile写入:
mypro:mypro.c
gcc mypro.c -o mypro # 前面的空格是一个tab的结果
即mypro依赖于mypro.c,而gcc myproc.c -o myproc 正是这种依赖方法。
在对Makefile写入上述依赖关系与依赖方法之后,我们就可以直接使用make命令了。

上面我们只是完成了Makefile的冰山一角,有个基础的认识。 我们可以用Makefile快速生成可执行,那有没有快速清理项目的呢?
我们可以在已经有的Makefile再进行增加:
mypro:mypro.c
gcc mypro.c -o mypro
.PHONY:clean
clean: # 可以没有依赖关系
rm -f myproc有了上面的Makefile,我们就可以进行项目清理了:
像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令⸺make clean,以此来清除所有的目标文件,以便重编译。

.PHONY **
但是一般我们这种clean的目标问文件,我们将它设置为伪目标,用.PHONY修饰,伪目标的特性是总是被执行的**。
我们也可以对第一组依赖关系用.PHONY修饰,来看看效果:
未修饰之前:

修饰之后:


有这些理解,我们来看看什么叫做总是被执行?
我们要知道,在编译的时候,默认老代码(以前编译过)是不会别重新编译的,很好理解,但是编译器是如何知道你是老代码还是新代码/更改过的代码。
我们知道:文件 = 内容 + 属性,我们可以用stat来查看文件的属性。

Modify: 内容变更,时间更新
Change:属性变更,时间更新
Access:常指的是文件最近一次被访问的时间。在Linux的早期版本中,每当文件被访问时,其atime都会更新。但这种机制会导致大量的IO操作。具体更新原则,不做过多解释
有了上面的了解,可以,对于每个文件的属性中都有其’‘时间’'属性,我们改变了文件的内容,它的Modify时间一定会变,所以通过Modify时间来进行对比判断它是老代码还是新代码/更改过的代码。和谁对比呢,就和对应的二进制文件中的相对应的时间属性进行对比。
但是,有了.PHONY,它不管老代码还是新代码/更改过的代码,都会重新编译,可见:
.PHONY会让make忽略源文件和可执行目标文件的M时间对比。
我们知道,C语言程序从.c文件到可执行文件会经过预处理、编译、汇编、链接,现在我们可以依次在Makefile中实现它:
1 mypro:mypro.o
2 gcc mypro.o -o mypro
3 mypro.o:mypro.s
4 gcc -c mypro.s -o mypro.o
5 mypro.s:mypro.i
6 gcc -S mypro.i -o mypro.s
7 mypro.i:mypro.c
8 gcc -E mypro.c -o mypro.i
9
10 .PHONY:clean
11 clean:
12 rm -f *.i *.s *.o mypro 执行make:

可见这样写也是可以的,那是怎么推导的呢?
当执行make命令时:

详细图解:

接下来,我们来总结分析一下make 是如何工作的,我们只 make 命令之后。那么:
有了上面一大串的铺垫,我们上面的Makefile文件只是一个较为简单的Makefile,只适合有一个源文件的时候使用,接下来,我要在此基础上在进行升级(适用于多个源文件的情况):
BIN=proc.exe # 定义变量:最终生成的可执行文件名
CC=gcc # 定义变量:使用的编译器
#SRC=$(shell ls *.c) # 方式一:用 shell 命令获取所有 .c 文件名
SRC=$(wildcard *.c) # 方式二:用 make 自带函数 wildcard 获取所有 .c 文件
OBJ=$(SRC:.c=.o) # 把所有 .c 文件替换成对应的 .o 文件(目标文件列表)
LFLAGS=-o # 链接选项(用于生成可执行文件)
FLAGS=-c # 编译选项(用于生成目标文件)
RM=rm -f # 定义删除命令
# === 构建目标规则 ===
$(BIN):$(OBJ)
@$(CC) $(LFLAGS) $@ $^ # 第一个@:不回显命令
@echo "linking ... $^ to $@"
# === 模式规则:编译每个 .c 文件成 .o 文件 ===
%.o:%.c
@$(CC) $(FLAGS) $<
@echo "compiling ... $< to $@"
# === 清理规则 ===
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN)
# === 测试输出规则(非编译) ===
.PHONY:test
test:
@echo $(SRC)
@echo $(OBJ)定义变量
BIN=proc.exe
CC=gcc
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)BIN:最终生成的可执行文件名CC:指定编译器为 gccSRC:自动收集当前目录下所有 .c 文件OBJ:把 .c 扩展名替换为 .o,即生成目标文件列表例如,当前目录中有:
main.c util.c则:
SRC = main.c util.c
OBJ = main.o util.o编译并链接
$(BIN):$(OBJ)
@$(CC) $(LFLAGS) $@ $^解释:
proc.exemain.o util.o.o 文件生成 proc.exe$@:代表目标(这里是 proc.exe)$^:代表所有依赖文件(这里是 main.o util.o)模式规则
%.o:%.c
@$(CC) $(FLAGS) $<这是 模式匹配规则:
% 代表任意匹配部分(相同名字的 .c 和 .o)$<:代表第一个依赖文件(即 .c 文件).c 编译成 .o等价于:
main.o: main.c
gcc -c main.c
util.o: util.c
gcc -c util.c清理规则
.PHONY: clean
clean:
$(RM) $(OBJ) $(BIN).PHONY:声明伪目标(表示 clean 不是文件名)$(RM):删除命令,这里是 rm -f执行:
make clean即可清空中间文件。
测试规则
.PHONY: test
test:
@echo $(SRC)
@echo $(OBJ)输出当前检测到的 .c 和 .o 文件,用于调试验证。
总结 这个 Makefile 利用了 变量 + 通配符 + 模式规则,实现了自动化、多文件可扩展的编译流程,可以认真学习学习。