赞
踩
作者:尹相楠
链接:https://zhuanlan.zhihu.com/p/149346441
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
首先,假设我们有如下几个代码文件:main.cpp functions.h function1.cpp function2.cpp (代码来自:Using make and writing Makefile(in C++ or C))。
— functions.h —
// functions.h
void print_hello();
int factorial(int n);
— function1.cpp —
// function1.cpp
#include “functions.h”
int factorial(int n){
if (n!=1){
return n*factorial(n-1);
else return 1;
}
— function2.cpp —
//function2.cpp
#include
#include<>
— main.cpp —
//main.cpp
int main(){
print_hello();
std::cout << “this is main” << std::endl;
std::cout << "The factorial of 5 is " << factorial(5) << std::endl;
return 0;
}
不用 makefile 如何编译?
如果不用 makefile,则需要按照下面的方式编译上述代码:
g++ -c function1.cpp
g++ -c function2.cpp
g++ -c main.cpp
g++ -o hello main.o function1.o function2.o
其中,g++ -c function1.cpp 会将源码编译成名为 function1.o 对象文件。如果不想采用默认的命名,也可以自定义文件名,例如:g++ -c function1.cpp -o fun1.o。
也可以用一行命令整合编译、链接的步骤:
g++ -o hello main.cpp function1.cpp function2.cpp
这种方式有很多弊端,例如:
首先需要确定 Makefile 的名字,需要设置成 Makefile 或者 makefile,而不能是其它版本(MakeFile, Make_file, makeFile,… )。其次,需要注意的是 Makefile 是缩进敏感的,在行首一定不能随便打空格。下面我们看一下第一版 Makefile。
all:
g++ -o hello main.cpp function1.cpp function2.cpp
clean:
rm -rf *.o hello
(注意上面代码片段的缩进,是一个而不是4个或者8个空格。)
其中 all 、clean的术语为 target,我也可以随意指定一个名字,例如 abc,真正执行编译的是它下面缩进行的命令。我们可以看到,这个命令和我们在命令行中手动敲的没有任何区别。因此,通过这个简单的 Makefile,就可以省去了每次手动敲命令的痛苦:只需要在命令行敲下 make 回车,即可完成编译。
clean 表示清除编译结果,它下方就是普通的命令行删除文件命令。命令行输入 make 将默认执行第一个 target (即 all)下方的命令;如要执行清理操作,则需要输入 make clean,指定执行 clean 这个 target 下方的命令。
这个 Makefile 虽然可以省去敲命令的痛苦,却无法选择性编译源码。因为我们把所有源文件都一股脑塞进了一条命令,每次都要编译整个工程,很浪费时间。第二版 Makefile 将解决这个问题。
第二版 Makefile
既然我们希望能够选择性地编译源文件,就不能像上一节那样把所有源文件放在一条命令里编译了,而是要分开写:
all: hello
hello: main.o function1.o function2.o
g++ main.o function1.o function2.o -o hello
main.o: main.cpp
g++ -c main.cpp
function1.o: function1.cpp
g++ -c function1.cpp
function2.o: function2.cpp
g++ -c function2.cpp
clean:
rm -rf *.o hello
上面的 Makefile 包含了一条重要的语法::。即,目标:目标依赖的文件。
顺着代码捋一下逻辑:
all: hello
hello: main.o function1.o function2.o
$(CC) $(LFLAGS) main.o function1.o function2.o -o hello
main.o: main.cpp
$(CC) $(CFLAGS) main.cpp
function1.o: function1.cpp
$(CC) $(CFLAGS) function1.cpp
function2.o: function2.cpp
$(CC) $(CFLAGS) function2.cpp
clean:
rm -rf *.o hello
上面的 Makefile 中,开头定义了三个变量:CC,CFLAGS,和 LFLAGS。其中 CC 表示选择的编译器(也可以改成 gcc);CFLAGS 表示编译选项,-c 即 g++ 中的 -c,-Wall 表示显示编译过程中遇到的所有 warning;LFLAGS 表示链接选项,它就不加 -c 了。这些名字都是自定义的,真正起作用的是它们保存的内容,因此只要后面的代码正确引用,将它们定义成阿猫阿狗都没问题。容易看出,引用变量名时需要用 $() 将其括起来,表示这是一个变量名。
第四版 Makefile
第三版的 Makefile 还是不够简洁,例如我们的 dependencies 中的内容,往往和 g++ 命令中的内容重复:
hello: main.o function1.o function2.o
$(CC)
(
L
F
L
A
G
S
)
m
a
i
n
.
o
f
u
n
c
t
i
o
n
1.
o
f
u
n
c
t
i
o
n
2.
o
−
o
h
e
l
l
o
我
们
不
想
敲
那
么
多
字
,
能
不
能
善
用
<
t
a
r
g
e
t
>
:
<
d
e
p
e
n
d
e
n
c
i
e
s
>
中
的
内
容
呢
?
这
就
需
要
引
入
下
面
几
个
特
殊
符
号
了
(
也
正
是
这
些
特
殊
符
号
,
把
M
a
k
e
f
i
l
e
搞
得
像
是
天
书
,
吓
退
了
很
多
初
学
者
)
:
(LFLAGS) main.o function1.o function2.o -o hello 我们不想敲那么多字,能不能善用 <target>:<dependencies> 中的内容呢?这就需要引入下面几个特殊符号了(也正是这些特殊符号,把 Makefile 搞得像是天书,吓退了很多初学者):
(LFLAGS)main.ofunction1.ofunction2.o−ohello我们不想敲那么多字,能不能善用<target>:<dependencies>中的内容呢?这就需要引入下面几个特殊符号了(也正是这些特殊符号,把Makefile搞得像是天书,吓退了很多初学者):@ ,
<
,
<,
<,^。
例如我们有 target: dependencies 对:all: library.cpp main.cpp
• $@ 指代 all ,即 target
• $< 指代 library.cpp, 即第一个 dependency
• $^ 指代 library.cpp 和 main.cpp,即所有的 dependencies
因此,本节开头的 Makefile 片段可以改为:
hello: main.o function1.o function2.o
$(CC) $(LFLAGS) $^ -o $@
而第四版 Makefile 就是这样的:
CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
all: hello
hello: main.o function1.o function2.o
$(CC) $(LFLAGS) $^ -o $@
main.o: main.cpp
$(CC) $(CFLAGS) $<
function1.o: function1.cpp
$(CC) $(CFLAGS) $<
function2.o: function2.cpp
$(CC) $(CFLAGS) $<
clean:
rm -rf .o hello
但是手动敲文件名还是有点麻烦,能不能自动检测目录下所有的 cpp 文件呢?此外 main.cpp 和 main.o 只差一个后缀,能不能自动生成对象文件的名字,将其设置为源文件名字后缀换成 .o 的形式?
第五版 Makefile
想要实现自动检测 cpp 文件,并且自动替换文件名后缀,需要引入两个新的命令:patsubst 和 wildcard。
5.1 wildcard
wildcard 用于获取符合特定规则的文件名,例如下面的代码:
SOURCE_DIR = . # 如果是当前目录,也可以不指定
SOURCE_FILE = $(wildcard $(SOURCE_DIR)/.cpp)
target:
@echo
(
S
O
U
R
C
E
F
I
L
E
)
m
a
k
e
后
发
现
,
输
出
的
为
当
前
目
录
下
所
有
的
.
c
p
p
文
件
:
.
/
f
u
n
c
t
i
o
n
1.
c
p
p
.
/
f
u
n
c
t
i
o
n
2.
c
p
p
.
/
m
a
i
n
.
c
p
p
其
中
@
e
c
h
o
前
加
@
是
为
了
避
免
命
令
回
显
,
上
文
中
m
a
k
e
c
l
e
a
n
调
用
了
r
m
−
r
f
会
在
t
e
r
m
i
n
a
l
中
输
出
这
行
命
令
,
如
果
在
r
m
前
加
了
@
则
不
会
输
出
了
。
5.2
p
a
t
s
u
b
s
t
p
a
t
s
u
b
s
t
应
该
是
p
a
t
t
e
r
n
s
u
b
s
t
i
t
u
t
i
o
n
的
缩
写
。
用
它
可
以
方
便
地
将
.
c
p
p
文
件
的
后
缀
换
成
.
o
。
它
的
基
本
语
法
是
:
(SOURCE_FILE) make 后发现,输出的为当前目录下所有的 .cpp 文件: ./function1.cpp ./function2.cpp ./main.cpp 其中 @echo 前加 @是为了避免命令回显,上文中 make clean 调用了 rm -rf 会在 terminal 中输出这行命令,如果在 rm 前加了 @ 则不会输出了。 5.2 patsubst patsubst 应该是 pattern substitution 的缩写。用它可以方便地将 .cpp 文件的后缀换成 .o。它的基本语法是:
(SOURCEFILE)make后发现,输出的为当前目录下所有的.cpp文件:./function1.cpp./function2.cpp./main.cpp其中@echo前加@是为了避免命令回显,上文中makeclean调用了rm−rf会在terminal中输出这行命令,如果在rm前加了@则不会输出了。5.2patsubstpatsubst应该是patternsubstitution的缩写。用它可以方便地将.cpp文件的后缀换成.o。它的基本语法是:(patsubst 原模式,目标模式,文件列表)。运行下面的示例:
SOURCES = main.cpp function1.cpp function2.cpp
OBJS = $(patsubst %.cpp, %.o, $(SOURCES))
target:
@echo $(SOURCES)
@echo $(OBJS)
输出的结果为:
main.cpp function1.cpp function2.cpp
main.o function1.o function2.o
5.3 综合两个命令
综合上述两个命令,我们可以升级到第五版 Makefile:
OBJS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
all: hello
hello: $(OBJS)
$(CC) $(LFLAGS) $^ -o $@
main.o: main.cpp
$(CC) $(CFLAGS) $< -o $@
function1.o: function1.cpp
$(CC) $(CFLAGS) $< -o $@
function2.o: function2.cpp
$(CC) $(CFLAGS) $< -o $@
clean:
rm -rf *.o hello
然而这一版的 Makefile 还有提升空间,它的 main.o,function1.o,function2.o 使用的都是同一套模板,不过换了个名字而已。第六版的 Makefile 将处理这个问题。
第六版 Makefile
这里要用到 Static Pattern Rule,其语法为:
targets: target-pattern: prereq-patterns
其中 targets 不再是一个目标文件了,而是一组目标文件。而 target-pattern 则表示目标文件的特征。例如目标文件都是 .o 结尾的,那么就将其表示为 %.o,prereq-patterns (prerequisites) 表示依赖文件的特征,例如依赖文件都是 .cpp 结尾的,那么就将其表示为 %.cpp。
通过上面的方式,可以对 targets 列表中任何一个元素,找到它对应的依赖文件,例如通过 targets 中的 main.o,可以锁定到 main.cpp。
下面是第六版的 Makefile
OBJS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
all: hello
hello: $(OBJS)
$(CC) $(LFLAGS) $^ -o $@
$(OBJS):%.o:%.cpp
$(CC) $(CFLAGS) $< -o $@
clean:
rm -rf *.o hello
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。