一、引入
多个文件编译生成可执行程序时,有两种方法:
- 法一:gcc -o test a.c b.c(内部实现机制:a.c和b.c都需要经历预处理、编译、汇编等过程后链接)
- 法二:gcc -c -o a.o a.c , gcc -c -o b.o b.c , gcc -o test a.o b.o (先各自编译不链接,最后一起链接)
当我们修改其中某个.c文件时,比如只修改a.c,那么法一需要所有文件都重新编译后链接,但其实b.c没有修改,再次编译是浪费时间和资源,这就是法一的缺点:每次修改都整体重新编译。
而法二就解决了法一的这个弊端,如只修改a.c文件,那么只需要执行gcc -c -o a.o a.c , gcc -o test a.o b.o即可,不需要重新编译b.c文件。
但是,当文件少的时候可以一个一个人为操作,一旦文件数量多了,手动操作就相当繁琐了,这就引入了Makefile,帮助我们自动编译。
那么,就产生一个问题,用Makefile如何实现法二在修改某个文件时的编译过程?
答:这个问题的本质就是Makefile如何判断哪些文件需要重新编译——比较时间。若.c文件时间比.o文件新,或者.o文件还没有生成,那么需要重新编译.c文件。这里.o文件就是目标文件,.c文件就是依赖文件。
二、Makefile规则
1.Makefile文件编写规则
目标(target)…: 依赖(prerequiries)…
<tab>命令(command)
如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目标文件”。
命令被执行的2个条件:依赖文件比目标文件新,或是 目标文件还没生成。
Makefile参考文档:GNU Make 使用手册(于凤昌中译版)_51CTO博客_gnu make 于凤昌
官方文档:https://www.gnu.org/software/make/manual/
2.make使用规则
- make程序需要一个所谓的Makefile文件来告诉它干什么。执行make的时候,系统回去当前目录下找Makefile文件。
- make [目标名] :若五目标,默认第一个目标
3.举例
以a.c, b.c文件为例:
test:a.o b.o
gcc -o a.o b.o
a.o:a.c
gcc -c -o a.o a.c
b.o:b.c
gcc -c -o b.o b.c
4.语法
4.1 通配符:% 和其他符号:$@ , $< , $^
上面的例子中,如果有1000个.c文件,若是每个文件都写一遍,那真的要累死。但仔细看第3行至第6行发现,其实格式都一样,只是因为文件名不同而写了多个。通配符%就可以解决这种重复但工作内容相似的问题:%.o代表所有满足这种格式的.o文件,%.c同理。
$@:表示目标文件
$<:表示第一个依赖文件
$^:表示所有依赖文件
所以,上面那个例子可以优化为:
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
4.2 假想目标:.PHONY:
上面的例子中只有一个目标test,执行make的时候可以写make,也可以写make test。一般Makefile里面不止一个目标。
这里假设需要一个清除所有编译生成的文件的操作,那么需要在Makefile里增加clean目标,如下:
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm -f *.o test
执行make clean就可以执行清楚操作了。
但其实这个有一个弊端:若该目录下存在clean文件,但在Makefile中clean没有依赖文件,所以就没有办法通过比较时间来判断rm指令需不需要执行,这时候即使我们make clean,系统会提示clean文件已经是最新,不回去执行rm操作。
解决办法就是将clean作为假想目标,方法如下:
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm -f *.o test
.PHONY:clean
这样,即使目录下有clean文件也不用担心啦。
4.3 一些变量::= , = , ?= , +=
- := :即时变量(简单变量),变量的值在定义时即刻确定
- = :延时变量,变量的值在使用时才确定
- ?= :延时变量,如果是第一次定义才起效,如果在前面该变量已定义则忽略这句
- += :附加变量,它是即时变量还是延时变量取决于前面的变量定义
举例:
A:=$(C)
B=$(C)
C:=123
D?=7788
all:
@echo A=$(A)
@echo B=$(B)
@echo D=$(D)
C+=123
make 结果:A= B=123 123 D=7788
make D=000结果:A= B=123 123 D=000
make C=000结果:A=000 B=000 D=7788
make C=000 D=000结果:A=000 B=000 D=000
5.函数
5.1 $(foreach var,list,test)
功能:对list中的每一个变量var,执行text操作
5.2 $(filter pattern...,text)
功能:在text中取出符合pattern格式的值
5.3 $(filter-out pattern...,text)
功能:在text中取出不符合pattern格式的值
5.4 $(wildcard pattern)
功能:pattern定义了文件名的格式,wildcard取出其中存在的文件
5.5 $(patsubst pattern,replacement,text)
功能:从list中取出每一个值,如果符合pattern,则替换为replacement,其他不符合的就不替换
示例:
A = a b c
B = $(foreach t,$(A),$(t).o)
C = a b c d/ /e
D = $(filter %/,$(C))
E = $(filter /%,$(C))
F = $(filter-out %/,$(C))
files = $(wildcard *.c)
files2 = a.c b.c c.c d.c e.c a.o b.o c.o d.o e.o
files3 = $(wildcard $(files2))
dep_file = $(patsubst %.c,%.d,$(files2))
all:
@echo B=$(B)
@echo D=$(D)
@echo E=$(E)
@echo F=$(F)
@echo files=$(files)
@echo files3=$(files3)
@echo dep_file=$(dep_file)
运行结果:
B=a.o b.o c.o
D=d/
E=/e
F=a b c /e
files=c.c a.c b.c
files3=a.c b.c c.c a.o b.o c.o
dep_file=a.d b.d c.d d.d e.d a.o b.o c.o d.o e.o
6.头文件依赖
参考文档:Linux Makefile 生成 *.d 依赖文件以及 gcc -M -MF -MP 等相关选项说明_Jerry.yl的博客-CSDN博客
include讲解:
https://www.cnblogs.com/cuckoos/articles/5049984.html
gcc指令 | 含义 |
---|---|
gcc -M c.c | 打印出依赖 |
gcc -M -MF c.d c.c | 生成c.d依赖文件 |
gcc -c -o c.o c.c -MD -MF c.d | 编译出c.o,且生成c.d依赖文件 |
举例:
已有文件a.c , b.c , c.c , c.h,文件内容如下:
//a.c文件内容:
#include <stdio.h>
int main(int argc,char* argv[])
{
func_b();
func_c();
return 0;
}
//b.c文件内容:
#include <stdio.h>
void func_b()
{
printf("This is B\n");
}
//c.c文件内容:
#include <stdio.h>
#include "c.h"
void func_c()
{
printf("This is C:%d\n",NUMC);
}
//c.h文件内容:
#define NUMC 1
之前的Makefile内容如下:
test:a.o b.o c.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm -f *.o test
.PHONY:clean
当我修改c.h的时候,make不会重新编译test。这就是上面的Makefile文件的弊端:不支持自动检测头文件。
①增加c.h头文件
test:a.o b.o c.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
c.o:c.c c.h
clean:
rm -f *.o test
.PHONY:clean
②如包含的依赖文件很多或者自己不知道,可不可以自动获取
test:a.o b.o c.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $< -MD -MF .$@.d
clean:
rm -f *.o test
.PHONY:clean
③自动获取了头文件依赖,并将其存到.c.o.d文件中,但是没有将其包含进来,修改c.h文件还是不能重新生成test
objs := a.o b.o c.o
dep_files := $(foreach f,$(objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
test:$(objs)
gcc -o test $^
# @echo dep_files=$(dep_files)
%.o:%.c
gcc -c -o $@ $< -MD -MF .$@.d
ifneq ($(dep_files),)
include $(dep_files)
endif
clean:
rm -f *.o test
disclean:
rm -vf $(dep_files) *.o test
.PHONY:disclean
.PHONY:clean
该Makefile文件就可以实现自动检测头文件了,当c.h文件修改时,会重新编译c.o文件。
7.CFLAGS
CFLAGS = -Werror
功能:将所有的警告都变成error,推荐使用
objs := a.o b.o c.o
dep_files := $(foreach f,$(objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
CFLAGS = -Werror
test:$(objs)
gcc -o test $^
# @echo dep_files=$(dep_files)
%.o:%.c
gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d
ifneq ($(dep_files),)
include $(dep_files)
endif
clean:
rm -f *.o test
disclean:
rm -vf $(dep_files) *.o test
.PHONY:disclean
.PHONY:clean
CFLAGS = -I.
功能:指定头文件目录为当前目录
一般我们习惯将所有头文件都放到include文件夹中,所以用CFLAGS = -Iinclude
三、注意
1.Makefile中的%标记和系统通配符*的区别在于:*是应用在系统中的,%是应用在这个Makefile文件中的。