一、Makefile命令规则
Makefile的命令规则如下:
目标:依赖
假设有一个Test.cpp文件,代码如下:
#include <iostream>
using namespace std;
int main() {
count << "===Test===" << endl;
return 0;
}
我们可以使用gcc或g++来 预处理
、汇编
、编译
、链接
,也可以利用Makefile来执行这些命令。
在Makefile中填入配置:
# 生成预处理文件
Test.i:Test.cpp
g++ -E Test.cpp -o Test.i
Test.i 是目标,Test.cpp 是依赖,整体的意思是:Test.i是依赖于Test.cpp生成的。
左右两边用冒号隔开,也就是Makefile命令规范:
目标:依赖
下一行是 g++ 命令,Test.i的生成是根据这条 g++ 命令生成的。g++前面必须含有分割符,而且必须是tab分割,不能是空格,否则在执行Makefile时不被识别。
执行该Makefile文件:
Makefile中可能由多个目标组成,make命令会找到Makefile中第一个目标执行。也可以指定一个目标执行:
make 目标
现在Makefile中只有一个目标,所以可以执行:
make Test.i
二、Makefile实现 预处理
、汇编
、编译
、链接
# 生成预处理文件
Test.i:Test.cpp
g++ -E Test.cpp -o Test.i
# 生成汇编文件
Test.s:Test.i
g++ -S Test.i -o Test.s
# 生成目标文件
Test.o:Test.s
g++ -c Test.s -o Test.o
# 生成链接文件(可执行文件)
Test:Test.o
g++ Test.o -o Test。
定义四个目标,分别是:Test.i
、Test.s
、Test.o
、Test
。
分别执行这四个目标:
make Test.i
make Test.s
make Test.o
make Test
可以完成 预处理
、汇编
、编译
、链接
。
但是,完成这写指令,还可以一步完成,我们的目标是执行 make 命令完成所有的步骤。
只需要改变一下Makefile中的目标顺序即可。
# 生成链接文件(可执行文件)
Test:Test.o
g++ Test.o -o Test
# 生成目标文件
Test.o:Test.s
g++ -c Test.s -o Test.o
# 生成汇编文件
Test.s:Test.i
g++ -S Test.i -o Test.s
# 生成预处理文件
Test.i:Test.cpp
g++ -E Test.cpp -o Test.i
输入make命令一次性执行完所有的目标:
注意:顺序要正确,必须形成逐步依赖关系。
三、makefile编译多个文件
Student.h
#ifndef _STUDENT_H_
#define _STUDENT_H_
#include <string>
using namespace std;
class Student {
private:
string name;
int age;
public:
void setName(string name);
string getName();
void setAge(int age);
int getAge();
};
#endif
Student.cpp
#include "Student.h"
void Student::setName(string name) {
this->name = name;
}
string Student::getName() {
return name;
}
void Student::setAge(int age) {
this->age = age;
}
int Student::getAge() {
return age;
}
Test.cpp
#include <iostream>
#include "Student.h"
using namespace std;
int main() {
Student* stu = new Student();
stu->setName("zhangsan");
stu->setAge(13);
cout << "姓名:" << stu->getName() << ", 年龄:" << stu->getAge() << endl;
return 0;
}
Makefile:
# 生成链接文件(可执行文件)
Test:Test.o Student.o
g++ Test.o Student.o -o Test
# 生成目标文件Test.o
Test.o:Test.s
g++ -c Test.s -o Test.o
# 生成目标文件Student.o
Student.o:Student.s
g++ -c Student.s -o Student.o
# 生成汇编文件Test.s
Test.s:Test.i
g++ -S Test.i -o Test.s
# 生成汇编文件Test.s
Student.s:Student.i
g++ -S Student.i -o Student.s
# 生成预处理文件Test.i
Test.i:Test.cpp
g++ -E Test.cpp -o Test.i
# 生成预处理文件Student.i
Student.i:Student.cpp
g++ -E Student.cpp -o Student.i
执行make命令之后,生成 预处理文件
、汇编文件
、目标文件
、可执行文件
。
四、makefile多文件管理
在编译期间,在同一个目录下生成多个文件,文件多了有点乱,这时,就需要分类管理,将不同格式的文件放入不同的文件夹。
修改Makefile配置:
# 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录
INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录
INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录
SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录
BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录
OBJ_DIR = ./obj
# 生成链接文件(可执行文件)
Test:${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o
g++ ${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o -o ${BIN_DIR}/Test
# 生成目标文件Test.o
${OBJ_DIR}/Test.o:${INFO_DIR}/Test.s
g++ -c ${INFO_DIR}/Test.s -o ${OBJ_DIR}/Test.o
# 生成目标文件Student.o
${OBJ_DIR}/Student.o:${INFO_DIR}/Student.s
g++ -c ${INFO_DIR}/Student.s -o ${OBJ_DIR}/Student.o
# 生成汇编文件Test.s
${INFO_DIR}/Test.s:${INFO_DIR}/Test.i
g++ -S ${INFO_DIR}/Test.i -o ${INFO_DIR}/Test.s
# 生成汇编文件Test.s
${INFO_DIR}/Student.s:${INFO_DIR}/Student.i
g++ -S ${INFO_DIR}/Student.i -o ${INFO_DIR}/Student.s
# 生成预处理文件Test.i
${INFO_DIR}/Test.i:${SRC_DIR}/Test.cpp
g++ -E ${SRC_DIR}/Test.cpp -o ${INFO_DIR}/Test.i
# 生成预处理文件Student.i
${INFO_DIR}/Student.i:${SRC_DIR}/Student.cpp
g++ -E ${SRC_DIR}/Student.cpp -o ${INFO_DIR}/Student.i
目录结构如下:
bin、include、info、obj、src文件夹需要手动创建;
bin文件夹存放可执行文件;
include文件夹存放头文件;
info文件夹存放预处理文件和汇编文件;
obj文件夹存放目标文件;
src文件夹存放c或cpp文件;
五、clean命令
在Makefile中添加:
clean:
del bin obj info
执行clean命令,可以删除 bin、obj、info文件夹中所有的文件。
make clean
六、Makefile通配符
%.o:表示一个xx.o文件
$@:表示目标文件
$<:表示第一个依赖文件
$^:所有不重复的依赖文件,以空格分开
$*:不包含扩展名的target文件名称
$+:所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
$?:所有时间戳比target文件晚的依赖文件,并以空格分开
由于 -o 后面输出的必然是目标文件,所以 -o 后面的输出文件可以替换为 $@:
# 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录
INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录
INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录
SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录
BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录
OBJ_DIR = ./obj
# 生成链接文件(可执行文件)
${BIN_DIR}/Test:${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o
g++ ${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o -o $@
# 生成目标文件Test.o
${OBJ_DIR}/Test.o:${INFO_DIR}/Test.s
g++ -c ${INFO_DIR}/Test.s -o $@
# 生成目标文件Student.o
${OBJ_DIR}/Student.o:${INFO_DIR}/Student.s
g++ -c ${INFO_DIR}/Student.s -o $@
# 生成汇编文件Test.s
${INFO_DIR}/Test.s:${INFO_DIR}/Test.i
g++ -S ${INFO_DIR}/Test.i -o $@
# 生成汇编文件Test.s
${INFO_DIR}/Student.s:${INFO_DIR}/Student.i
g++ -S ${INFO_DIR}/Student.i -o $@
# 生成预处理文件Test.i
${INFO_DIR}/Test.i:${SRC_DIR}/Test.cpp
g++ -E ${SRC_DIR}/Test.cpp -o $@
# 生成预处理文件Student.i
${INFO_DIR}/Student.i:${SRC_DIR}/Student.cpp
g++ -E ${SRC_DIR}/Student.cpp -o $@
clean:
del bin obj info
$<、$^ 和 % 结合使用,可以最大程度上简化配置:
# 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录
INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录
INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录
SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录
BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录
OBJ_DIR = ./obj
# 生成链接文件(可执行文件)
${BIN_DIR}/Test:${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o
g++ $^ -o $@
# 生成所有目标文件 %:任意字符通配符 $<:表示第一个依赖文件
${OBJ_DIR}/%.o:${INFO_DIR}/%.s
g++ -c $< -o $@
# 生成所有汇编文件 %:任意字符通配符 $<:表示第一个依赖文件
${INFO_DIR}/%.s:${INFO_DIR}/%.i
g++ -S $< -o $@
# 生成所有预处理文件 %:任意字符通配符 $<:表示第一个依赖文件
${INFO_DIR}/%.i:${SRC_DIR}/%.cpp
g++ -E $< -o $@
clean:
del bin obj info
以上配置显然已经简单了很多。
另外,头文件已经放在include目录,cpp文件已经放在了src目录,所以需要指定头文件位置,由两种方法可以指定:
【方法一】在cpp中修改头文件位置
将
#include "Student.h"
改成
#include "../include/Student.h"
【方法二】g++命令后面指定头文件路径
加入 -I $(INC_DIR)
导入头文件
# 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录
INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录
INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录
SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录
BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录
OBJ_DIR = ./obj
# -I 指定头文件
CXXFLAGS=-I $(INC_DIR)
# 生成链接文件(可执行文件)
${BIN_DIR}/Test:${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o
g++ $(CXXFLAGS) $^ -o $@
# 生成所有目标文件 %:任意字符通配符 $<:表示第一个依赖文件
${OBJ_DIR}/%.o:${INFO_DIR}/%.s
g++ $(CXXFLAGS) -c $< -o $@
# 生成所有汇编文件 %:任意字符通配符 $<:表示第一个依赖文件
${INFO_DIR}/%.s:${INFO_DIR}/%.i
g++ $(CXXFLAGS) -S $< -o $@
# 生成所有预处理文件 %:任意字符通配符 $<:表示第一个依赖文件
${INFO_DIR}/%.i:${SRC_DIR}/%.cpp
g++ $(CXXFLAGS) -E $< -o $@
clean:
del bin obj info
g++出现了多次,也可以使用变量代替,并且新增TAGERT和OBJS变量:
# 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录
INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录
INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录
SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录
BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录
OBJ_DIR = ./obj
# -I 指定头文件
CXXFLAGS=-I $(INC_DIR)
# CC 指定编译器 gcc 、 g++
CC=g++
#最终生成的目标,
TAGERT=Test
# 目标文件
OBJS=${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o
# 生成链接文件(可执行文件)
${BIN_DIR}/${TAGERT}:${OBJS}
${CC} $(CXXFLAGS) $^ -o $@
# 生成所有目标文件 %:任意字符通配符 $<:表示第一个依赖文件
${OBJ_DIR}/%.o:${INFO_DIR}/%.s
${CC} $(CXXFLAGS) -c $< -o $@
# 生成所有汇编文件 %:任意字符通配符 $<:表示第一个依赖文件
${INFO_DIR}/%.s:${INFO_DIR}/%.i
${CC} $(CXXFLAGS) -S $< -o $@
# 生成所有预处理文件 %:任意字符通配符 $<:表示第一个依赖文件
${INFO_DIR}/%.i:${SRC_DIR}/%.cpp
${CC} $(CXXFLAGS) -E $< -o $@
clean:
del bin obj info
以上配置还存在一定的弊端,OBJS变量指定了目标文件,此时的目标文件是需要指定具体目标文件的。
七、wildcard函数查找
$(wildcard pattern)
:pattern定义了文件名的格式,wildcard取出其中存在的文件。
# wildcard查找当前目录下所有cpp文件
SRCS=$(wildcard $(SRC_DIR)/*.cpp)
此时 SRCS 就是 src目录下所有cpp格式的文件。
八、patsubst函数替换
$(patsubst pattern,replacement,$(var))
:从var中将符合patern格式的内容,替换为replacement。
# wildcard查找当前目录下所有cpp文件
SRCS=$(wildcard $(SRC_DIR)/*.cpp)
# notdir 去除掉绝对路径,只保留名字
# patsubst 把字串 $(notdir $(SRCS)) 符合模式[%.cpp]的单词替换成[%.o]
OBJS=$(patsubst %.cpp,$(OBJ_DIR)/%.o,$(notdir $(SRCS)))
patsubst 可以解决指定明确目标文件的弊端,改进弊端之后的配置如下:
# 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录
INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录
INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录
SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录
BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录
OBJ_DIR = ./obj
# -I 指定头文件
CXXFLAGS=-I $(INC_DIR)
# CC 指定编译器 gcc 、 g++
CC=g++
#最终生成的目标,
TAGERT=Test
# wildcard查找当前目录下所有cpp文件
SRCS=$(wildcard $(SRC_DIR)/*.cpp)
# notdir 去除掉绝对路径,只保留名字
# patsubst 把字串 $(notdir $(SRCS)) 符合模式[%.cpp]的单词替换成[%.o]
OBJS=$(patsubst %.cpp,$(OBJ_DIR)/%.o,$(notdir $(SRCS)))
# wildcard *.cpp 当前目录下所有c文件
SRCS=$(wildcard $(SRC_DIR)/*.cpp)
# 生成链接文件(可执行文件)
${BIN_DIR}/${TAGERT}:${OBJS}
${CC} $(CXXFLAGS) $^ -o $@
# 生成所有目标文件 %:任意字符通配符 $<:表示第一个依赖文件
${OBJ_DIR}/%.o:${INFO_DIR}/%.s
${CC} $(CXXFLAGS) -c $< -o $@
# 生成所有汇编文件 %:任意字符通配符 $<:表示第一个依赖文件
${INFO_DIR}/%.s:${INFO_DIR}/%.i
${CC} $(CXXFLAGS) -S $< -o $@
# 生成所有预处理文件 %:任意字符通配符 $<:表示第一个依赖文件
${INFO_DIR}/%.i:${SRC_DIR}/%.cpp
${CC} $(CXXFLAGS) -E $< -o $@
clean:
del bin obj info
九、Makefile的变量
Makefile的变量分为两种:及时变量
、延时变量
【1】 `:=`:即时变量,该变量的值即刻确定,在定义时就被确定了;
【2】`=`:延时变量,该变量的值,在使用时才确定;
【3】`?=`:延时变量,第一次定义才起效,如果前面被定义过了就忽略这句;
【4】`+=`:附加,它是即时变量还是延时变量取决于前面的定义;
十、函数遍历
$(foreach val,list,text)
:对于list(通常用空格隔开)里的每一个变量执行text操作
# 定义一个list
LIST=a b c
# 用f代表A中的各个变量,执行第三个参数的操作。
# foreach 遍历关键字,f:LIST中的每个变量,
RESULT=$(foreach f,$(LIST),$(f).result)
all:
@echo RESULT=$(RESULT)
输出结果:
RESULT=a.result b.result c.result
十一、filter函数过滤
$(filter pattern...,text)
:在text里面取出符合pattern格式的值
$(filter-out pattern...,text)
:在text里面取出不符合pattern格式的值
# 定义变量C
C= a b c d/
# 在变量C中取出符合%/的值
D=$(filter %/,$(C))
# 在变量C中取出不符合%/的值
E=$(filter-out %/,$(C))
all:
@echo D=$(D)
@echo E=$(E)
输出结果:
D=d/
E=a b c
十二、CXXFLAGS补充
上面已经定义了 CXXFLAGS 变量:
CXXFLAGS=-I $(INC_DIR)
我们还可以对它进行补充,比如:
# -I 指定头文件
CXXFLAGS=-I $(INC_DIR) -Wall -O2 -std=c++11 -frtti -fexceptions
可以指定的FLAG有:
【1】-Werror:会把所有警告当成错误
【2】-I: 该选项用于指定编译程序时依赖的头文件路径
【3】-On: 这是一个优化选项,如果在编译时指定该选项,则编译器会根据n的值(n取0到3之间)
对代码进行不同程度的优化,其中-O0 表示不优化,n的值越大,优化程度越高
【4】-L: 库文件依赖选项,该选项用于指定编译的源程序依赖的库文件路径,库文件可以是静态链接库,也可以是动态链接库
【5】-Wall: 允许发出gcc能提供的所有有用的警告,也可以用-W(warning)来标记指定的警告
【6】-std=c++11:C++11标准
【7】-frtti 和 -fexceptions:关闭 exceptions、rtti 可以减小程序的占用的空间和提升程序的运行效率,
关闭后可能带来一些不兼容问题,使用 typeid 运算符必须开启 RTTI
[本章完...]