1、什么是make?
make是个命令,是个可执行程序,是个工具,用来解析Makefile文件的命令,这个命令存放在/usr/bin/目录下
-rwxr-xr-x 1 root root 250K 2月 15 2022 make
-rwxr-xr-x 1 root root 4.8K 2月 15 2022 make-first-existing-target //Perl脚本编写的程序会由make命令触发执行,用来检测文件是否被修改,没有修改跳过编译
Debian系安装make工具:sudo apt install make
2、什么是makefile?
Makefile是个文件,这个文件中描述了咱们程序的编译规则,咱们执行make命令的时候,make命令会在当前目录下寻找Makefile文件,根据Makefile文件里的规则,编译咱们的程序。
注意Makefile文件是咱们程序员根据自己的程序,编写的编译规则文件。
3、make的主要好处
一、大量代码的关系维护,大项目中源代码比较多,手工维护、编译时间比较长而且编译命令比较复杂,难以记忆和维护,把代码维护命令和编译命令写在Makefile文件中,然后再用make工具解析编好规则的Makefile文件,自动执行相应命令,可实现代码的合理编译。
二、减少重复编译时间,在改动其中一个文件的时候,能判断哪些文件被修改过,可以只对该文件进行重新编译,然后重新链接所有的目标文件,节省编译时间。
4、一张图解释:make和:Makefile关系
5、Makefile的编写语法规则
目标:依赖文件列表
命令列表
1、目标:通常是要产生的文件名称,目标可以是可执行文件或其他object文件,也可是一个动作的名称
2、依赖文件列表:由多个依赖文件组成,每个依赖文件是用来输入而产生目标的文件,一个目标通常有几个依赖文件(也可以没有)
3、命令列表:由多个命令组成(也可以没有),代表make执行的动作,注意一个规则可以包含多个命令,存在多个命令时,每个命令占一行
举例:将gcc -o hello main.c hello.c hi.c good.c编写成Makefile内容
Makefile文件内容:
main:main.c hello.c hi.c good.c
gcc -o main main.c hello.c hi.c good.c
clean:
rm main
5.1、Makefile变量
5.1.1、Makefile变量
Makefile变量类似C语言的宏Makefile被make工具解析时,其中的变量会被展开。变量的作用:保存文件名列表,保存文件目录列表保存编译器名,保存编译参数,保存编译输出。
Makefile变量分为三种:系统环境变量、自定义变量、预定义变量
系统环境变量:make工具解析Makefile前,读取系统环境变量并设置为Makefile的变量
自定义变量:在Makefile文件中定义的变量,make工具传给Makefile的变量都是自定义变量
预定义变量:自动变量,都是设置好的变量(可以理解为规则)
5.1.2、Makefile变量编写规则
变量名=变量值 引用变量:${变量名}
注意:
1、注意Makefile的变量名字允许以数字开头
2、变量区分大小写
3、变量通常在Makefile文件的头部定义
4、变量可在Makefile的任何地方使用
5.1.3、预定义变量
Makefile中我们通常只需要定义自定义变量和重新定义系统环境变量,而不需要定义预定义变量。
预定义变量通常是通用的,像正则表达式的元字符,具有特殊的含义。
$@ # 目标名
$< # 依赖文件列表的第一个文件
$^ # 依赖文件列表中去除重复文件的部分
% # 通配符
AR # 归档维护程序的程序名,默认值为ar
ARFLAGS # 归档维护程序的选项
AS # 汇编程序的程序名,默认值为as
ASFLAGS # 汇编程序的选项
CC # C编译器的名称,默认为cc
CFLAGS # C编译器的选项
CPP # C预编译器的名称,默认值为$(CC) -E
CPPFLAGS # C预编译器的选项
CXX # C++编译器的名称,默认为g++
CXXFLAGS # c++编译器的选项
6、make命令格式
make [-f file] [targets] [target] ...
1、通常我们把Makefile文件编写好规则,直接敲make,make默认从工作目录中寻找名为GNUmakefile或makefile或Makefile的文件内容的第一个目标(也有人称规则)进行解析编译。而 -f选项可以指定以上名字以外的文件作为Makefile输入文件。
2、[target]:若使用make命令时没有指定目标,则make工具默认会实现Makefile文件内的第一个目标,然后退出。若指定了目标,目标可以是一个或多个,多个目标之间默认用空格隔开。
7、makefile案例
源代码目录结构
.
├── main.c
├── Makefile
├── myfunc.c
└── myfunc.h
0 directories, 4 files
其中除Makefile以外的文件,我写在上一章csdn博文,参考gcc的编译C语言的过程-CSDN博客
7.1、简单级别
touch创建Makefile文件,写入如下内容
Makefile
my_execfile:main.o myfunc.o
gcc -o my_execfile main.o myfunc.o
main.o:main.s
gcc -c main.s -o main.o
my_func.o:myfunc.s
gcc -c myfunc.s -o myfunc.o
main.s:main.i
gcc -S main.i -o main.s
my_func.s:myfunc.i
gcc -S myfunc.i -o myfunc.s
main.i:main.c
gcc -E main.c -o main.i
myfunc.i:myfunc.c
gcc -E myfunc.c -o myfunc.i
clean:
rm my_execfile *.i *.s *.o
这里我把上一章的预处理、编译、汇编、链接的产物和过程集中写在Makefile文件中,并使用clean命令规则清理临时产物包括最终的可执行文件my_execfile。通过简单的把以上步骤汇总在Makefile文件中,只需要敲make就可以输出所有临时产物包括可执行文件,敲make clean可以清理所有中间产物和可执行文件,这对于代码维护很方便,不仅简洁美观,编译也省时。
效果
输出所有中间产物:*.i *.s *.o和可执行文件my_execfile
清理所有产物
7.2、普通级别
Makefile
1 #自定义变量
2 EXEC=my_execfile #我们的最终想要的可执行文件
3 CC=gcc #这里对gcc编译器也定义变量,是因为Makefile可以处理多种编译器的规则
4 OBJ=main.o myfunc.o #我们的目标文件
5 OBJ1=main.o
6 OBJ2=myfunc.o
7 OBJ3=main.s
8 OBJ4=myfunc.s
9 OBJ5=main.i
10 OBJ6=myfunc.i
11 SOUR=main.c
12 SOUR1=myfunc.c
13 FLAGS=-Wall # 这里输出gcc编译的所有warning和error
14
15 $(EXEC):$(OBJ)
16 $(CC) -o $(EXEC) $(OBJ)
17 $(OBJ1):$(OBJ3)
18 $(CC) -c $(OBJ3) -o $(OBJ1) $(FLAGS)
19 $(OBJ2):$(OBJ4)
20 $(CC) -c $(OBJ4) -o $(OBJ2) $(FLAGS)
21 $(OBJ3):$(OBJ5)
22 $(CC) -S $(OBJ5) -o $(OBJ3) $(FLAGS)
23 $(OBJ4):$(OBJ6)
24 $(CC) -S $(OBJ6) -o $(OBJ4) $(FLAGS)
25 $(OBJ5):$(SOUR)
26 $(CC) -E $(SOUR) -o $(OBJ5) $(FLAGS)
27 $(OBJ6):$(SOUR1)
28 $(CC) -E $(SOUR1) -o $(OBJ6) $(FLAGS)
29 clean:
30 rm $(EXEC) $(OBJ5) $(OBJ6) $(OBJ3) $(OBJ4) $(OBJ)
相对于简单处理编写Makefile文件的工作,这里我把部分编译命令通过变量的形式替换,只需要修改2~12行的数据名称,gcc的四个编译过程就可以被执行输出。clean命令进一步的缩小删除范围,可以精准到具体要删除的文件,防止其他非该Makefile的输出产物被删除。
效果
输出所有产物
清理所有产物
7.3、升级
Makefile
# 自定义变量
EXEC=my_execfile # 最终的可执行文件名
OBJ=main.o myfunc.o # 目标文件
CC=gcc # 编译器
FLAGS=-Wall # 编译选项,输出所有警告和错误信息
# 生成最终可执行文件
$(EXEC): $(OBJ)
$(CC) -o $@ $^ $(FLAGS)
# 从汇编文件生成目标文件
%.o: %.s
$(CC) -c $< -o $@ $(FLAGS)
# 从汇编文件生成汇编代码文件
%.s: %.i
$(CC) -S $< -o $@ $(FLAGS)
# 从预处理文件生成汇编文件
%.i: %.c
$(CC) -E $< -o $@ $(FLAGS)
# 清理生成的文件
clean:
rm -f $(EXEC) $(OBJ) $(OBJ:.o=.s) $(OBJ:.o=.i)
# 生成所有中间文件的伪目标
all: $(OBJ:.o=.s) $(OBJ:.o=.i)
# 默认的伪目标
.PHONY: all clean
使用预定义变量和自定义变量结合,并加额外的伪目标以生成所有的中间产物和清理所有的中间产物,使用变量替换技巧将$(OBJ)的.o替换为其他中间产物,并使用.PHONY声明all和clean为伪目标,防止出现同名文件而产生歧义。
效果
输出所有产物
清理所有产物
8、小结
实际上,中间产物并不是我们最终需要的目标文件,我们需要可执行文件文件就可以了。希望以上步骤对于了解make的编译原理有所帮助。
# 自定义变量
EXEC=my_execfile # 最终的可执行文件名
OBJ=main.c myfunc.c # 目标文件
CC=gcc # 编译器
FLAGS=-Wall # 编译选项,输出所有警告和错误信息
# 生成最终可执行文件
$(EXEC): $(OBJ)
$(CC) -o $@ $^ $(FLAGS)
# 清理生成的文件
clean:
rm $(EXEC)
# 默认的伪目标
.PHONY: clean
效果