要想成为一个”资深”程序员,gcc, gdb, make这三个命令是必须要熟练掌握的。今天介绍Makefile即make操作。
一,什么是Makefile
make 操作必须先编写Makefile文件。什么是Makefile? 可以将Makefile理解为脚本。这种脚本就是在多文件编译时的一个“自动化编译程序”。在大型工程中,常常成百上千的源文件需要编译链接,我们一个个编译太耗时间,而且当其中一个文件需要修改时,我们还要又得把所有文件编译一遍链接,这简直悲催~
Makefile要做的就是将这些文件组织起来进行联合编译,不需要我们一个个编译,并且当其中文件改变时,Makefile能够智能的识别出来,并只对相应的文件进行自动编译,省时省力!
虽然make工具有很多智能识别机制,但它无法自动了解它所面对的工程源文件的组成及依赖关系,这就是开发人员编写Makefile脚本要做的工作,将文件依赖关系描述清楚,只需要执行make命令,脚本就能自动 的编译连接成可执行文件了。
二,Makefile的组成及规则
一个完整的makefile文件通常由五个部分组成:
◆显示规则
显示规则是指主动编写描述规则,用于指示在何种状态下更新哪些目标文件,即编写makefile时需要明确指出各目标文件所依赖的源文件的集合,以及编译本目标文件所需的命令。
◆隐含规则
指用make中默认的编译方式进行编译,即make工具可以根据目标文件的类型自动推导出的规则(由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略书写Makefile ,这是由make所支持的)
◆变量定义
为提升语句的灵活性,在make脚本中可以使用变量,来代表一个字符串,一组编译命令或一组文件名。(在makefile 中我们要定义一系列的变量,变量一般都是字符串,这个有点类似于C语言中的宏,当makefile 被执行时,其中的变量都会被扩展到相应的引用位置上)
◆makefile指示符
指示符告诉make工具,当程序在读取makefile文件时要执行的动作。
(文件指示包括三部分,一个是在一个makefile中引用另外一个makefile,就像C语言中的include一样,另一个是指根据某些情况制定MakeFile中的有效部分,就像C语言中的预编译#if一样,还有就是定义一个多行的命令。)
◆注释
Makefile中将#作为注释标识符
【注】如果此行的第一个非空字符为#,则此行为注释; 如果此行的结尾存在一个反斜杠(\),则下一行也被视为注释行;如果在代码中需要使用字符#,就要使用转义字符#来表示此字符并非一个注释符。
三,Makefile的语法
makefile规则是用于描述在何种状态下,用何种命令创建何种文件。如下:
Target … : Dependency …
<Tab>Command
…
…
◆Target代表目标文件,既可以是一个目标代码文件,也可以是一个可执行文件,还可以是一个伪目标。
◆Dependency代表生成上述目标文件所需要的文件(即依赖文件)。
◆Command代表响应命令,即由依赖文件生成目标文件所需要的命令。
【注意】规则中的命令必须以Tab键开头,make 工具会将所有以Tab键开头的行当成命令交给shell 执行。
为方便阅读,对于一个较长的命令行可以书写到多行上,注意行与行之间要用“\”链接。
在makefile中,规则主要包括两部分,一是依赖关系,一是生成目标的方法。
其中规则的顺序很重要,因为,makefile中只应该有一个最终目标,其他的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么,一般来说,定义在Makefile中的目标可能会很多,但是第一条规则中的目标将被确立为最终的目标,如果第一条规则中目标有多个,那么,第一个目标将会成为最终目标,
make所要完成的也就是这个目标。
四,make是如何工作的
在默认的方式下,也就是我们只输入make命令。那么
1. make会在当前目录下找名字叫做”Makefile/makefile”的文件。
2. 如果找到,它就会找文件中第一个目标文件(traget),并将这个文件当做最终的目标文件。
3. 若target文件不存在,或是target所依赖的后面的.o文件的文件修改时间要比target这个文件新,那么,它就会执行后面所定义的命令来生成target这个目标文件。
4. 当生成目标文件的.o文件也不存在时,它就会执行生成.o文件的命令,即用.o的依赖文件(.h或.c)来生成.o文件,在用.o文件(可能是多个)生成最终的target目标文件(即链接完的可执行文件)。
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在寻找的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错。值得注意的是,makefile只负责将整个工程编译链接,如程序本身有bug,那就不是makefile文件要管的事了。
五,简单的Makefile编写
一个简单的main()调用两个fun()的程序。这个工程包含的文件有 fun1.cpp; fun1.h; fun2.cpp; fun2.h; main.cpp; makefile.
文件fun1.h:
#pragma once
#include <iostream>
using namespace std;
void fun1();
文件fun1.cpp:
#include "fun1.h"
void fun1()
{
cout << "This is fun1()." << endl;
}
文件fun2.h:
#pragma once
#include <iostream>
using namespace std;
void fun2();
文件fun2.cpp:
#include"fun2.h"
void fun2()
{
cout << "This is fun2()." << endl;
}
文件main.cpp:
#include "fun1.h"
#include "fun2.h"
int main()
{
fun1();
fun2();
return 0;
}
重点来了,联合编译fun1.cpp,fun2.cpp,main.cpp需要编写的makefile文件如下:
//main就是要生成的最终目标文件,而main依赖main.o,fun1.o,fun2.o来联合生成
main:main.o fun1.o fun2.o
//生成mian的操作,因为缺少main.o,fun1.o,fun2.o所以程序向下寻找
g++ -o main main.o fun1.o fun2.o
main.o:main.cpp //生成main.o需要main.cpp
g++ -o main.o -c main.cpp //生成main.o的操作
fun1.o:fun1.cpp //生成fun1.o需要fun1.cpp
g++ -o fun1.o -c fun1.cpp //生成fun1.o的操作
fun2.o:fun2.cpp //生成fun2.o需要fun2.cpp
g++ -o fun2.o -c fun2.cpp //生成fun2.o的操作
//将clean定义为伪文件目标,防止执行make命令的文件夹中有同名的clean文件,不能够有效的执行下面的 make clean 操作。
.PHONY:clean
clean: //生成完main后执行make clean将所有.o文件删除
rm *.o
当编写完makefile文件,在命令行输入 “make” 操作,就会生成可执行文件main了,这个文件就是main.cpp,fun1.cpp,fun2.cpp联合编译的目标文件了。
makefile其实还是很复杂的,大型工程的makefile要牵扯很多问题,有需求的同学可以继续深入学习。当然对于我们平常自己编写的代码,以上makefile知识就完全够了。