前文中,我们有多少个.c文件,就需要build 出来多少个.o文件
假设我们的项目很大,怎么管理这些 .c文件呢?
这里就要学习一个make文件的编写了。
makefile 本质上是一个脚本语言
脚本语言实际上就是将一系列命令放在一起执行
makefile 的用途:
项目代码编译管理
节省编译项目时间
一次编译终身受益
Makefile 工作内容:
makefile 会把遇见的第一条指令作为他的终极指令
但是如果开发者在前面加了关键字 ALL
就会把ALL:后面的 做为 makefile 的终极目标
因此一般,我们都会写ALL
ALL:a.out
make file的写法:
命名只有两种,一种叫做makefile 一种叫做Makefile
一个规则,
目标:依赖条件
(一个tab缩进)命令
hello:hello.c
gcc hello.c -o hello
解释:
目标是生成hello 这个可执行文件
依赖条件的意思是:要生成hello这个目标文件,需要依赖于 hello.c这个文件
然后缩进,这个是语法格式要求
命令的意思是:怎么使用 hello.c生成hello这个目标文件
基本原则的原文:
若想生成目标, 检查规则中的额依赖条件是否存在,如不存在,则寻找是否有规则用来生成该依赖文件。
好,我们把这段话从自己刚才写的例子理解一下:
上述我们写的时候 是目标是 hello ,依赖条件是hello.c
我们改写一下 先从hello.c生成 hello.o
然后从hello.o生成目标文件hello
hello:hello.o
gcc hello.o -o hello
hello.o:hello.c
gcc -c hello.c -o hello.o
hunandede@hunandede-virtual-machine:~/day03/makefilestudy$ ls
hello.c makefile
hunandede@hunandede-virtual-machine:~/day03/makefilestudy$ make
gcc -c hello.c -o hello.o
gcc hello.o -o hello
hunandede@hunandede-virtual-machine:~/day03/makefilestudy$
1.学习这个之前,我们将昨天的部分整理过来并 按照g++的方法手动build 一下
文件内容如下,head 中存放.h文件
src 文件夹下放着所有的.cpp文件
g++ src/test.cpp src/addfunc.cpp src/devfunc.cpp src/mulfunc.cpp src/subfunc.cpp -o a.out -I ./head
如上,我们build 出来了一个 a.out文件,如下是执行 a.out文件
hunandede@hunandede-virtual-machine:~/day03/makefilestudy2$ ./a.out
a+b = 15
a-b = 5
a*b = 50
a/b = 2
2. 改造成makefile 的写法
a.out:addfunc.o subfunc.o mulfunc.o devfunc.o test.o
g++ addfunc.o subfunc.o mulfunc.o devfunc.o test.o -o a.out
addfunc.o:addfunc.cpp
g++ -c addfunc.cpp -o addfunc.o -I../head
subfunc.o:subfunc.cpp
g++ -c subfunc.cpp -o subfunc.o -I../head
mulfunc.o:mulfunc.cpp
g++ -c mulfunc.cpp -o mulfunc.o -I../head
devfunc.o:devfunc.cpp
g++ -c devfunc.cpp -o devfunc.o -I../head
test.o:test.cpp
g++ -c test.cpp -o test.o -I../head
执行make 就会生成a.out文件,然后我们运行 a.out文件
hunandede@hunandede-virtual-machine:~/day03/makefilestudy2/src$ ./a.out
a+b = 15
a-b = 5
a*b = 50
a/b = 2
这里有一个问题:我们上面为什么要将所有的.c分开写,转成.o,然后再使用使用所有的.o生成 a.out呢? 直接 g++ addfunc.cpp subfunc.cpp mulfunc.cpp divfunc.cpp -o a.out 写起来不是更方便吗?
原因是 当addfunc.cpp的代码有改动后,重新build的时候,这么一行的写法会让所有的.cpp文件编译成.o,然后将所有的.o文件再链接成 a.out文件。
实际上只有 addfunc.cpp改动了,其他的都没有变化,让其他的跟着重新build出来自己的.o文件是很没有必要的。因此要分开写。
回顾 g++编译的四个阶段
预处理:头文件展开,宏替换,注释去掉
编译:检查语法错误,将c语言变成汇编语言,消耗时间和系统资源最多
汇编:将汇编语言变成 二进制指令
链接:数据段合并以及地址回填,将函数库中相应的代码组合到目标文件中
引申出的另一个问题:g++是如何知道addfunc.cpp有变化了,那就要看file 属性中的ctime了,changed time,当一旦发现ctime有变化,就会重新编译了。实际上应该是说 当addfunc.cpp的ctime比addfunc.o的时间晚,则会重新编译 addfunc.cpp
1.目标的时间必须晚于依赖条件的时间,否则,更新目录。
2.依赖条件如果不存在找寻新的规则去产生依赖。
新的问题:
看起来是可以完全fix此类问题,好,又有一个新问题,我们的需求变化了,增加了一个 模余.cpp文件,用来求余数的。
那么我们就要改动 makefile文件以支持,类似如下的写法,
虽然也可以实现功能,但是很笨拙
a.out:addfunc.o subfunc.o mulfunc.o devfunc.o moyufunc.o test.o
g++ addfunc.o subfunc.o mulfunc.o devfunc.o test.o -o a.out
addfunc.o:addfunc.cpp
g++ -c addfunc.cpp -o addfunc.o -I../head
subfunc.o:subfunc.cpp
g++ -c subfunc.cpp -o subfunc.o -I../head
mulfunc.o:mulfunc.cpp
g++ -c mulfunc.cpp -o mulfunc.o -I../head
devfunc.o:devfunc.cpp
g++ -c devfunc.cpp -o devfunc.o -I../head
moyufunc.o:moyufunc.cpp
g++ -c moyufunc.cpp -o moyufunc.o -I../head
test.o:test.cpp
g++ -c test.cpp -o test.o -I../head
解决这样笨拙写法的方案是 使用两个函数,类似通配符
二个函数
wildcard 和 patsubst
wildcard :通配符,白搭牌的意思
patsubst: 是用来匹配和替换的。
src = $(wildcard *.cpp)
obj = $(patsubst %.cpp,%.o, $(src))
src = $(wildcard *.cpp) #src = addfunc.cpp, subfunc.cpp mulfunc.cpp devfunc.cpp test.cpp
obj = $(patsubst %.cpp, %.o, $(src)) #obj = addfunc.o subfunc.o mulfunc.o devfunc.o test.o
ALL:a.out
a.out:$(obj)
g++ $(obj) -o a.out
addfunc.o:addfunc.cpp
g++ -c addfunc.cpp -o addfunc.o -I../head
subfunc.o:subfunc.cpp
g++ -c subfunc.cpp -o subfunc.o -I../head
mulfunc.o:mulfunc.cpp
g++ -c mulfunc.cpp -o mulfunc.o -I../head
devfunc.o:devfunc.cpp
g++ -c devfunc.cpp -o devfunc.o -I../head
test.o:test.cpp
g++ -c test.cpp -o test.o -I../head
clean:
-rm -rf $(obj) a.out
#a.out:addfunc.o subfunc.o mulfunc.o devfunc.o test.o
# g++ addfunc.o subfunc.o mulfunc.o devfunc.o test.o -o a.out
#addfunc.o:addfunc.cpp
# g++ -c addfunc.cpp -o addfunc.o -I../head
#subfunc.o:subfunc.cpp
# g++ -c subfunc.cpp -o subfunc.o -I../head
#mulfunc.o:mulfunc.cpp
# g++ -c mulfunc.cpp -o mulfunc.o -I../head
#devfunc.o:devfunc.cpp
# g++ -c devfunc.cpp -o devfunc.o -I../head
#test.o:test.cpp
# g++ -c test.cpp -o test.o -I../head
clean 清除不想要的文件
还有一个问题,我们每次build 出来的都有.o文件,实际上这个.o文件是中间文件,没啥用,
可以使用 clean指令删除这些不想要的.o文件,也可以将 a.out
clean:
-rm -rf $(obj) a.out
使用时,先用 make clean -n,看一下要删除哪些东西,确认没有问题后,
再使用 make clean 真正的删除。
-rm 这个 -的意思是 即使这个文件不存在,也不会报错
三个自动变量
$@ 在规则的命令中,表示规则中的目标,也就是说,只能出现在如下的“命令”中
目标:依赖条件
(一个tab缩进)命令
$^ 在规则的命令中,表示所有的依赖条件
$< 在规则的命令中,表示第一个依赖条件
如果将该变量应用在模式规则中,它可将依赖条件列表中的依赖条件,依次取出,套用模式规则。
那就在改一版
src = $(wildcard *.cpp) #src = addfunc.cpp, subfunc.cpp mulfunc.cpp devfunc.cpp test.cpp
obj = $(patsubst %.cpp, %.o, $(src)) #obj = addfunc.o subfunc.o mulfunc.o devfunc.o test.o
ALL:a.out
a.out:$(obj)
g++ $^ -o $@ #changed the 规则的命令中,只能是在命令行改动
addfunc.o:addfunc.cpp
g++ -c $< -o $@ -I../head
subfunc.o:subfunc.cpp
g++ -c $< -o $@ -I../head
mulfunc.o:mulfunc.cpp
g++ -c $< -o $@ -I../head
devfunc.o:devfunc.cpp
g++ -c $< -o $@ -I../head
test.o:test.cpp
g++ -c $< -o $@ -I../head
clean:
-rm -rf $(obj) a.out
#a.out:$(obj)
# g++ $(obj) -o a.out
#addfunc.o:addfunc.cpp
# g++ -c addfunc.cpp -o addfunc.o -I../head
#subfunc.o:subfunc.cpp
# g++ -c subfunc.cpp -o subfunc.o -I../head
#mulfunc.o:mulfunc.cpp
# g++ -c mulfunc.cpp -o mulfunc.o -I../head
#devfunc.o:devfunc.cpp
# g++ -c devfunc.cpp -o devfunc.o -I../head
#test.o:test.cpp
# g++ -c test.cpp -o test.o -I../head
#clean:
# -rm -rf $(obj) a.out
#a.out:addfunc.o subfunc.o mulfunc.o devfunc.o test.o
# g++ addfunc.o subfunc.o mulfunc.o devfunc.o test.o -o a.out
#addfunc.o:addfunc.cpp
# g++ -c addfunc.cpp -o addfunc.o -I../head
#subfunc.o:subfunc.cpp
# g++ -c subfunc.cpp -o subfunc.o -I../head
#mulfunc.o:mulfunc.cpp
# g++ -c mulfunc.cpp -o mulfunc.o -I../head
#devfunc.o:devfunc.cpp
# g++ -c devfunc.cpp -o devfunc.o -I../head
#test.o:test.cpp
# g++ -c test.cpp -o test.o -I../head
4.模式规则
我们发现上面的代码是有些是很相似的
#都是
%o:%.cpp
g++ -c $< -o $@ -I../head
addfunc.o:addfunc.cpp
g++ -c $< -o $@ -I../head
subfunc.o:subfunc.cpp
g++ -c $< -o $@ -I../head
mulfunc.o:mulfunc.cpp
g++ -c $< -o $@ -I../head
devfunc.o:devfunc.cpp
g++ -c $< -o $@ -I../head
test.o:test.cpp
g++ -c $< -o $@ -I../head
#都是
%o:%.cpp
g++ -c $< -o $@ -I../head
#因此可以直接替换,这个就是规则模式
5.还有问题:模式规则的疑问fix-使用静态模式规则
我们前面通过模式规则将.cpp变成.o
那么如果我们有些.cpp不想变成.o文件呢?
或者说我们还有一些c文件也要变成.o文件呢?
静态模式规则就是用来fix上述问题的。
$(obj1):%o:%.cpp
g++ -c $< -o $@ -I../head
$(obj2):%o:%.c
g++ -c $< -o $@ -I../head
因此可以再改动一版
src = $(wildcard *.cpp) #src = addfunc.cpp, subfunc.cpp mulfunc.cpp devfunc.cpp test.cpp
obj = $(patsubst %.cpp, %.o, $(src)) #obj = addfunc.o subfunc.o mulfunc.o devfunc.o test.o
ALL:a.out
a.out:$(obj)
g++ $^ -o $@ #changed the 规则的命令中,只能命令行
%.o:%.cpp
g++ -c $< -o $@ -I../head
#addfunc.o:addfunc.cpp
# g++ -c $< -o $@ -I../head
#subfunc.o:subfunc.cpp
# g++ -c $< -o $@ -I../head
#mulfunc.o:mulfunc.cpp
# g++ -c $< -o $@ -I../head
#devfunc.o:devfunc.cpp
# g++ -c $< -o $@ -I../head
#test.o:test.cpp
# g++ -c $< -o $@ -I../head
clean:
-rm -rf $(obj) a.out
#a.out:$(obj)
# g++ $(obj) -o a.out
#addfunc.o:addfunc.cpp
# g++ -c addfunc.cpp -o addfunc.o -I../head
#subfunc.o:subfunc.cpp
# g++ -c subfunc.cpp -o subfunc.o -I../head
#mulfunc.o:mulfunc.cpp
# g++ -c mulfunc.cpp -o mulfunc.o -I../head
#devfunc.o:devfunc.cpp
# g++ -c devfunc.cpp -o devfunc.o -I../head
#test.o:test.cpp
# g++ -c test.cpp -o test.o -I../head
#clean:
# -rm -rf $(obj) a.out
#a.out:addfunc.o subfunc.o mulfunc.o devfunc.o test.o
# g++ addfunc.o subfunc.o mulfunc.o devfunc.o test.o -o a.out
#addfunc.o:addfunc.cpp
# g++ -c addfunc.cpp -o addfunc.o -I../head
#subfunc.o:subfunc.cpp
# g++ -c subfunc.cpp -o subfunc.o -I../head
#mulfunc.o:mulfunc.cpp
# g++ -c mulfunc.cpp -o mulfunc.o -I../head
#devfunc.o:devfunc.cpp
# g++ -c devfunc.cpp -o devfunc.o -I../head
#test.o:test.cpp
# g++ -c test.cpp -o test.o -I../head
删除各个版本演化注释的,就变成下面的样子了
src = $(wildcard *.cpp) #src = addfunc.cpp, subfunc.cpp mulfunc.cpp devfunc.cpp test.cpp
obj = $(patsubst %.cpp, %.o, $(src)) #obj = addfunc.o subfunc.o mulfunc.o devfunc.o test.o
ALL:a.out
a.out:$(obj)
g++ $^ -o $@ #changed the 规则的命令中,只能命令行
%.o:%.cpp
g++ -c $< -o $@ -I../head
clean:
-rm -rf $(obj) a.out
6. makefile脚本的运行
1.直接make 就会执行makefile 中的所有指令
2.使用make -n 不会真的执行指令,会将指令显示出来
2.使用make clean -n 查看要清除的文件
3.使用make clean 真正清除文件
7.make执行时候的问题
从上述的学习知道,我们会在makefile 中写一个
ALL:a.out
clean:
-rm -rf $(obj) a.out
如果这时候我们当前文件下,还有一个 clean的文件,
再使用make clean的时候就会有问题。
因此我们还有改动 makefile文件,写一个伪目标
.PHONY: clean ALL 这句话的意思是:即使当前文件夹下有 clean的文件,或者有ALL的文件,会认为clean文件和ALL文件是伪目标。不会执行真正的文件,而是执行make 命令参数。例如:make clean
src = $(wildcard *.cpp) #src = addfunc.cpp, subfunc.cpp mulfunc.cpp devfunc.cpp test.cpp
obj = $(patsubst %.cpp, %.o, $(src)) #obj = addfunc.o subfunc.o mulfunc.o devfunc.o test.o
ALL:a.out
a.out:$(obj)
g++ $^ -o $@ #changed the 规则的命令中,只能命令行
%.o:%.cpp
g++ -c $< -o $@ -I../head
clean:
-rm -rf $(obj) a.out
.PHONY: clean ALL
8.优化,可以将-g -Wall -I 作为参数加进去
src = $(wildcard *.cpp)
obj = $(patsubst %.cpp, %.o, $(src))
ALL:a.out
myargs = -g -Wall -I../head
a.out:$(obj)
g++ $^ -o $@ ${myargs}
%.o:%.cpp
g++ -c $< -o $@ ${myargs}
clean:
-rm -rf $(obj) a.out
.PHONY: clean ALL
9.练习 1
要求 inc 目录下放置.h文件
obj目录下放置生成的.obj文件
src目录下放置.cpp 文件
最终生成的 a.out可执行文件和makefile文件在同一级目录
写出makefile文件
src1 = $(wildcard ./src/*.cpp) #这时候 src1 = ./src/addfunc.cpp ./src/subfunc.cpp ......
obj = $(patsubst ./src/%.cpp, ./obj/%.o, $(src1))
#这里要注意 将参数3中 ,包含参数1的部分,替换为参数2 ,这时候参数3取出来的值是 ./src/addfunc.cpp
#因此需要将 patsubst %.cpp 换成 .src/%.cpp, 将第二个参数%.o改成 ./obj/%o,
#这样 % 才能代表同样的部分,也就是文件名的部分, obj的值就是 ./obj/addfunc.o ./obj/subfunc.o ......
ALL:a.out
myargs = -g -Wall -I./inc
a.out:$(obj)
g++ $^ -o $@ ${myargs}
./obj/%.o : ./src/%.cpp
g++ -c $< -o $@ ${myargs}
clean:
-rm -rf $(obj) a.out
.PHONY: clean ALL
10.练习 2 ,本地有多个 main程序,一键生成
假设有a.cpp, b.cpp, c.cpp 每个.cpp文件中都有main函数,
我们的目标是实现
g++ a.cpp -o a
g++ b.cpp -o b
g++ c.cpp -o c
src = $(wildcard *.cpp)
target = $(patsubst %.cpp, %, $(src))
ALL: $(target)
%:%cpp
gcc &< -o $@
clean:
-rm -rf $(target)
.PHONY: clean ALL