前面我们介绍了c++的编译工具,使用g++实现对单个文件,多个文件,静态库动态库的编译;我们继续以该项目为例讲解;
g++ 的编译使用入门教程-点这里查看
我们继续以前面的目录解构为例,这里给出上一节的目录如下:
- mutilFilesDemo
- include // 头文件目录
- HelloTools.h
- Prints.h
- libs // 库子项目目录
- ToolLibs.h
- ToolLibs.cpp
- libToolLibs.a // 静态库
- src // 源码目录
- module // 源码模块
- Prints.cpp // Prints类
- HelloTools.cpp // HelloTools类
- main.cpp // main类
我们简单再回顾直接使用g++的编译上面项目的流程;该项目包含一个自项目静态库;所以我们需要首先编译子项目mutilFilesDemo/libs
生成库文件libToolLibs.a
,然后再进行编译主项目
step.1 编译子项目
# 进入子项目
cd mutilFilesDemo/libs
g++ -c ToolLibs.cpp
ar crv libToolLibs.a ToolLibs.o
step.编译主项目
# 回到主项目
cd mutilFilesDemo
# 编译并链接到静态库
g++ main.cpp ./src/HelloTools.cpp ./src/modules/Prints.cpp -L ./libs -l ToolLibs -o main
这样我们就完成整个编译了;
倘若,我们开发一个大工程,有很多的模块,很多子项目、代码文件,同时依赖很多静态库、动态库的话 ,显然采用这种方式工作量是非常大的。
于是我们创造出了一个make的工具及Makefile文件,Makefile文件记录编译相关的策略及配置项,我们只需要一个make命令即可完成编译,是不是很优雅?
首先,Makefile的本质其实可以理解为一个构建shell配置脚本;说白了,就是将我们前面写的g++ xxx.cpp -o xxx
等一长串命令,预先写好,然后输入make
命令,就自动执行了
理论上来说,你写shell脚本也可以实现c++的编译,再退一万步,只要你愿意,你可以一条条g++
地输入
我们先看一个Makefile的最简单例子
Makefile
文件
help :
@echo "help info"
dist :
@mkdir dist
clean :
@rm -rf dist
我们执行一下make
,输入help info
字符串
$ make
help info
其实就是执行了@echo help info
这条命令(前面加了@,表示不显示命令字符串自本身)
这个
Makefile
没啥用,然而,确实就是一个合法的Makefile
文件,所以其实我们可以从侧面窥探出这个Makefile
其实并没有什么高深的技术,它本质就是执行一系列的脚本,而不管你执行的是啥东西(当然,make工具添加许多非常方便的脚本函数,都是在g++手动编译过程中遇到的麻烦事转摸索过来的经验工具函数);Makefile
更多的是一种自动化构建思想;
对于Makefile
的规则,始终默认执行文件中的第一个标签的脚本,这里即是help
;如果希望执行其他标签的脚本可在make命令的后面跟上相应的标签,如:make dist
实际上,Makefile官方介绍中,是这样介绍这个基本规则的,Makefile的编写基本规则如下
target: prerequisites
recipe
target: 通常是程序生成(输出)的一个或多个文件名,例如:可执行文件或目标文件;它也可以是要执行任务的名称,例如用于清理生成文件的 clean 任务
prerequisites: 先决条件是用于生成 target 文件的输入文件或是完成 target 任务前需要先执行的任务 。一个 target 可以没有先决条件,也可以有一个或多个先决条件(比如,编译的依赖文件,这里还隐藏着的规则是,如果这些依赖发生了改变,则目标文件将会重新编译,着这个规则可以大大减少我们重新编译的效率)
recipe: 中文翻译为菜谱,它是 make 用于生成 target 文件或完成 target 任务而执行一系列 shell 命令。这些命令可以放在同一行里,也可以每个命令占一行。值得注意的是,recipe 默认以制表符开头,而不是空格
其实写到这里,你已经有办法可以写一个简单的符合Makefile的c++构建脚本了;
我们把Makefile
再改造成如下内容:
build :
@echo "开始编译c++"
g++ main.cpp ./src/HelloTools.cpp ./src/modules/Prints.cpp -L ./libs -l ToolLibs -o main
@echo "end"
执行一下make
命令
$ make
开始编译c++
g++ main.cpp ./src/HelloTools.cpp ./src/modules/Prints.cpp -L ./libs -l ToolLibs -o main
end
编译成功!
当然Makefile
的功能远不止这些,我们继续学习一下Makefile的基本语法规则
=
除外),如下所示objs = main.o model.o view.o controller.o
注意变量的赋值有两种,一种是取直接赋值(
:=
),领外一种是最后计算赋值(=
);遗憾的我们熟悉的编程赋值语法是这一种最后计算赋值=
;这意味着如果赋值内容存在变量,而该变量在后面的脚本中,被改变了,那边这个赋值是采用最终值来计算赋值的;所以搞不清楚用法的话,最好变量赋值最好只做一次赋值
,避免踩坑
$()
的方式使用,如下所示build: $(objs)
g++ $(objs) -o app
变量的其他用法
$^
表示所有的依赖文件
$@
表示生成的目标文件
$<
代表第一个依赖文件
这些变量与前面自定义的变量本质一样,都是可定义修改的,只不过这些变量全局的环境变量,可全局使用;
CFLAGS
、CXXFLAGS
、CPPFLAGS
选项 | 说明 |
---|---|
-c | 用于把源码文件编译成 .o 对象文件,不进行链接过程 |
-o | 用于连接生成可执行文件,在其后可以指定输出文件的名称 |
-g | 用于在生成的目标可执行文件中,添加调试信息,可以使用GDB进行调试 |
-Idir | 用于把新目录添加到include路径上,可以使用相对和绝对路径,“-I.”、“-I./include”、“-I/opt/include” |
-Wall | 生成常见的所有告警信息,且停止编译,具体是哪些告警信息,请参见GCC手册,一般用这个足矣! |
-w | 关闭所有告警信息 |
-O | 表示编译优化选项,其后可跟优化等级0\1\2\3,默认是0,不优化 |
-fPIC | 用于生成位置无关的代码 |
-v | (在标准错误)显示执行编译阶段的命令,同时显示编译器驱动程序,预处理器,编译器的版本号 |
LDFLAGS
选项 | 说明 |
---|---|
-llibrary | 链接时在标准搜索目录中寻找库文件,搜索名为liblibrary.a 或 liblibrary.so |
-Ldir | 用于把新目录添加到库搜索路径上,可以使用相对和绝对路径,“-L.”、“-L./include”、“-L/opt/include” |
-Wl,option | 把选项 option 传递给连接器,如果 option 中含有逗号,就在逗号处分割成多个选项 |
-static | 使用静态库链接生成目标文件,避免使用共享库,生成目标文件会比使用动态链接库大 |
LIBS
如: LIBS = -lpthread -lm -lpthread -liconv
make 中的函数用于处理 Makefile 文件中的文本,例如:计算操作的文件列表,“菜谱”中使用的命令等。
$(function arguments)
例如常用的函数实例
SRC = $(wildcard *.cpp)
OBJ = $(patsubst %.cpp, %.o, $(SRC))
build:
@echo