NDK<第五篇>:Makefile(windows环境)

一、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文件:

image.png

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.iTest.sTest.oTest

分别执行这四个目标:

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命令一次性执行完所有的目标:

image.png

注意:顺序要正确,必须形成逐步依赖关系。

三、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命令之后,生成 预处理文件汇编文件目标文件可执行文件

image.png
四、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

目录结构如下:

image.png
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

[本章完...]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 1. 概述 1.1 前言 之前在Linux下写C/C++都是直接输命令行,虽然有使用make的经历,但没有自己动手...
    kophy阅读 3,865评论 0 19
  • Makefile 文件描述了整个工程的编译、链接等规则。其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建...
    LeoLongl阅读 1,367评论 0 0
  • Makefile基础语法 目标:依赖Tab 命令目标:一般指要编译的目标,也可以是一个动作依赖:指执行当前目...
    arkliu阅读 219评论 0 0
  • Android开发时,有时候需要移植第三方库进项目,需要对第三方库进行交叉编译生成Android可用的动态库或者静...
    书文换酒钱阅读 1,496评论 0 1
  • 1 三要素: 依赖:判断依赖是否存在,若存在则判断跟目标的时间戳关系 目标:判断目标是否存在,若存在则退出;否则执...
    roger_ting阅读 289评论 0 0