当前课程知识点:基于Linux的C++ > 第十二讲 Linux系统编程基础 > 12.12 makefile文件(一) > LinuxCPP1212
这一讲的最后一节 makefile
makefile 是一个文件
它专门用来指导编译我们的程序用的
它专门用来指导编译我们的程序用的
过去在 Linux 下边没有集成开发环境
我们写的 C 程序也好 C++ 程序也好
都需要通过命令行的手段对它进行编译
都需要通过命令行的手段对它进行编译
当这个文件 当这个程序很大
文件很多的时候
那么手工地去编译它
实际上是非常费劲的
所以为了方便程序员
就可以写一个 makefile
然后用 make 这个命令去调用它
make 命令就负责我们 C/C++ 代码的编译与链接
make 命令就负责我们 C/C++ 代码的编译与链接
它会去读取这个 makefile 文件中
里面的内容
根据它的内容去执行你的命令
完成整个程序的编译和链接
makefile 做的就是这个事情
它就是给出 make 命令
编译和链接我们程序时的规则
在 Linux 操作系统下面
makefile 的文件有三个命名
叫 GNUmakefile
小写的 makefile 和大写 Makefile
“M” 大写 三个名字都可以
不过我建议同学们用后边两个
因为第一个 GNUmakefile
是 GNU 专用的
如果你写的代码那个不是 GNU 的
那么它可能就不能用
makefile 有自己的固定的文件格式
也有自己的语法规则
它能够处理基本的语法元素
能够支持变量 能够进行条件判断
能够写循环 还能够写函数
你看 它实际上
本质上就是一个编程语言了 很复杂
makefile 在早期写 C/C++ 的代码的时候
非常非常有用
但现在同学们有了集成开发环境
makefile 的使用
频率就不像过去那么多
但是对于一个 Linux 程序员来讲
了解 makefile 还是很有必要的
所以我们这一节
就会简单地介绍 makefile
makefile 文件格式是固定的:
“target : prerequisites 换行 Tab 键 commands”
必须按照这个方式来
makefile 本身是由一系列的规则来构成的
规则是干嘛用的
就是我们建构目标的先决条件是什么
以及怎么样来完成这个目标的建构
target 就是我们的建构目标
prerequisites 就是我们的建构先决条件
commands 就是它的建构命令
因为它是一个行格式的
makefile 里面所有的内容
都是以行的形式来进行解析的
所以它的格式特别要求
所有的命令前是要有 Tab 键的
冒号前是它的目标
冒号后是建构这个目标
所需要的先决条件
下边紧跟着的一行或多行
就成了它的建构命令
格式是固定的
你如果没有指定目标
那么 make 在运行的时候
就会执行这个 makefile 里边的缺省的第一目标
就会执行这个 makefile 里边的缺省的第一目标
第一目标就是它缺省的
你没有指定 它就用它的第一个目标
如果第一个目标能够解决 它就会解决
如果第一个目标还需要其它的目标才能够解决
如果第一个目标还需要其它的目标才能够解决
那么它就会不断地回溯
所以它实际上是构造了一系列的规则链
这样的一个描述构造一个规则
下面可能再构造另外一个规则
它会形成一个规则链
如果 prerequisites 有一个以上的文件比 target 新
如果 prerequisites 有一个以上的文件比 target 新
下面的命令它就会去执行
以确保我们的 target 目标是有效的
是最新的 就是这个意思
target 就是我们的目标
通常情况下
它应该是编译期的一个文件名
我们指定我们要创建的这个目标
要建构的对象是什么
那就是一个文件名
我们要生成什么样的文件
这就是我们的 target
这是一般情况
事实上它可以是个执行文件
还可以纯粹地就是一个标签
它是一个操作的名字
它不对应于真实的文件
这种东西我们称它为伪目标
target 这个地方可以写一个目标
也可以写很多个目标
这个没关系
对于规则的构建来讲
targets 那是一系列的目标
很多个 target 可以写在一起
中间有一个空格来分隔开 就完了
它后面的先决条件也可以有好多个
中间也是用空格来分隔开的
特别需要说明的是
每一个那样的 targets 目标集
它其实都构成了一组处理规则
它决定了那个目标或者那些目标
如何被构造
当那个目标在这个规则下
不能被构造的时候
就是它还需要其它的先决条件
才能构造我们的先决条件的时候
那么它就会找那个先决条件的构成的规则 如果那个构成规则还需要其它构成规则
那么它就会找那个先决条件的构成的规则 如果那个构成规则还需要其它构成规则
那么它就会找那个先决条件的构成的规则 如果那个构成规则还需要其它构成规则
它会继续去找其它的构成规则
这就意味着在查找 makefile 文件的时候
它会形成一个规则链
这一点是需要特别去注意的
prerequisites 就是它的先决条件
为生成我们这个目标所需要的一些文件或者一些目标
为生成我们这个目标所需要的一些文件或者一些目标
那就是先决文件或先决目标
有时我们称之为前置条件
你得有了它 才能构建我们的目标
prerequisites 要做的事其实就是这个东西
prerequisites 要做的事其实就是这个东西
一般的情况下呢
它是一些空格分隔的一些文件名
它指定我们的目标
建构的时候的判断标准
我们应该从什么地方
来建构我们的目标 就这个意思
只要有一个先决文件 它不是最新的
那么我们就需要重建我们的目标
target 就需要被重建
makefile 最主要的规则就在这里
对于 prerequisites 这个先决条件来讲
如果这个先决条件本身需要被重建
那么它就会匹配先决条件的那个对应的目标规则
那么它就会匹配先决条件的那个对应的目标规则
然后就执行对应的命令
commands 就是命令
由一行或多行 shell 命令来组成的
每一个命令前都有一个 Tab 键
你说为了美观
我前面用两个 Tab 键可不可以呢 可以
我用 Tab 键和空格可不可以呢 也可以
但是你要记得 Tab 键必须是开头的
顶头字符必须是 Tab 键 不能是空格
特别注意这一条
target 那个目标头前面是不能有 Tab 键的
但是为了美观 你也可以缩进
但那个时候只能用空格去缩进
不能使用 Tab 键缩进
Tab 键只能用在命令行的前面
这是第一个需要注意到的
所有的命令都是为了指示
我们如何建构目标的
一般是生成我们的目标文件所需要的命令序列
一般是生成我们的目标文件所需要的命令序列
我第一步做什么 第二步做什么
第三步做什么
它是好多条命令合成的
注意这一点
特别需要说明的是
每行命令
都是在一个单独的 shell 里运行的
也就是说它们运行在不同的进程里
彼此之间是没有继承关系的
所以你不能够简单地
在两条命令中间传递数据
不可以的
可以在 shell 里定义变量
可以用 shell 变量来传递数据
但是特别要记住
两行命令 它们是在不同的 shell
不同的进程里面运行的
你直接简单地认为它们可以传递数据
可以共用一些信息是不妥当的
解决方法当然也是有的
如果你真地需要它们共享信息
你可以把这多条命令
把它合并成一条
每条命令中间用分号分隔开
后面命令紧跟着写在那一行
就是把这两条命令合并成一条
让它们在一个 shell 下面运行
如果这一行很长 你觉得写不下
那么你可以在行尾用 “\” 来折行
把后面那个部分写在第二行
“\” 折行 这是允许的
还有一种方案
当它很长的时候 你也不想折行
你又想分行写 那怎么办呢
你可以把整个这个规则
前面加上一个 “.ONESHELL :”
加这个指示就表示把所有这些命令都在一个 shell 里面把它运行完
加这个指示就表示把所有这些命令都在一个 shell 里面把它运行完
就是这个意思
你按照这个方式书写 command
如果一个目标后边不带先决条件
那么它就表示一个伪目标
仅仅是一个操作的名称
而不是一个文件名
你比如讲 如果我在编译完成之后
我想删除编译期生成的一些二进制的目标文件
我想删除编译期生成的一些二进制的目标文件
就是中间结果我不想要了
我就可以写一个伪目标 clean
后面跟着一个命令 “rm –f *.o”
把所有 “.o” 的文件全都删掉
我不要了
我可执行文件都编译出来
我还要 “.o” 干嘛呢
不要了 OK
我就可以 “rm –f *.o” 把它删掉
写了一个伪目标 “clean :”
这就是它的目标
当你执行编译命令的时候
你就可以执行 make
后面带参数 clean
它就会执行这个目标
调用 rm 命令把所有的 “.o” 文件全部删除
调用 rm 命令把所有的 “.o” 文件全部删除
这就是伪目标
但是你要注意
当这个当前目录下有一个文件
名字本身就叫 “clean” 的时候
你 make clean 就有可能导致问题
它就会去 make 你那 clean 那个文件去了
为了确保不会出现文件查找的情况
那么你可以在 clean 的前边
填上一个特殊的指示 “.PHONY : clean”
表示 clean 明确的是一个伪目标
而不是一个文件
那么 make 就会跳过文件查找过程
直接执行你的伪目标
按照这个方式去书写
有一点是需要说明的
clean 这个伪目标一般放在我们 makefile 文件的末尾
clean 这个伪目标一般放在我们 makefile 文件的末尾
放在前面 尤其放在最顶头
它会作为缺省目标的
所以一般我们把它放在末尾
表示做最后的扫尾工作
在 Linux 操作系统下
有一些伪目标的定义惯例
你并不需要一定按照这个方式
但是如果整个程序员社区
基本上都是按照这个模式
来书写伪目标的名字
那么你不按照这个方式来写
它本身就有点奇怪
让别人就不太容易看得懂
你的 makefile 到底写的是什么
所以正常情况下
我们应该遵照这样的惯例
比如说伪目标 all
它就用来表示所有目标的目标
当你一个工程项目
包括很多个可执行文件
想要同时编译的时候
你就可以一个伪目标 all
将所有的文件一股脑、一口气全部都编译完
将所有的文件一股脑、一口气全部都编译完
这就是 all 目标做的事
clean 就是删除它的中间文件
install 就是安装
print 就是输出我们
改变过的源文件的信息
tar 就是对它备份
dist 就不仅仅备份
我还要形成一个压缩包 以利于分发
你比如说我们看这样一个例子
假设我们有一个程序
主文件名字叫 main.c
它需要使用到 library 这个库
这个库当然也是我们写的
包括 “.h” 和 “.c” 这两个文件
所以我们就可以按照这个模式写
我们的目标就叫 prog 冒号
它需要使用到的两个先决条件
就是 “main.o” 和 “library.o”
两个目标文件
我要用它来构造我们的可执行程序
就这个意思 我们怎么构造呢
当然调用 cc 编译器 “-o prog”
就是输出文件名叫 “prog”
然后需要使用 “main.o”、“library.o”
这两个目标文件来链接它
“main.o” 怎么来呢
当然要编译 “main.c”、“library.h”
“main.c” 需要使用到 “library.h”
所以我们按照这个模式写
编译的时候就是 “cc –c main.c”
这是它的编译命令
那么 “library.o” 这个文件怎么来呢
它的先决条件就是 “library.c”、“library.h”
然后写 “.PHONY : clean”
“clean :” “rm main.o library.o”
把这两个目标文件清除掉就完了
这个就是我们的 makefile 文件
然后你在命令行下就可以运行 make
它就会执行缺省的第一条命令
替我们生成我们的 prog 文件
如果你执行 make clean
它就会把所有的 “.o”
都给你清除掉
删除 没了
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口