一、问题的出现
我们在Linux上编译一个main.c文件时通常会用到这个命令:
gcc -o main main.c
假如我们同时写了多个文件,比如有sel.c、select.c、sub.c等等,我们就会这样来编译这些文件
gcc -o main main.c sel.c select.c sub.c
但如果其中一个文件出现错误,我们修改了之后就会必须将其他文件里的相关内容也进行修改但如果文件较多,我们不方便修改的话会大大降低效率,这个时候我们就会用到makefile文件。
二、MakeFile文件的定义
MakeFile文件就类似于windows上的IDE,他俩做着相同的工作。MakeFile关系着整个工程的编译规则。一个工程中的源文件不计其数,其按类型、功能、模块放在不同的目录下。MakeFile定义了一系列的规则来决定哪些先编译,哪些文件需要后编译,以及更复杂的功能操作。
MakeFile带来的好处:”自动化编译“。写好之后只需要一个make命令。整个工程就可以自动化编译,可以极大得提高效率。make是一个命令工具,是解释MakeFile中指令的一个工具
三、MakeFile文件的简单运用
我们现在简单写几个代码来看一下MakeFile的优点和好处。
在Linux上,我们先创建几个文件main.c、sub.c、add.c、mux.c、div.c和my_math.h这几个文件。并在里面写如下的代码:
my_math.h
//my_math.h
#ifndef __MY_MATH_H
#define __MY_MATH_H
int my_add(int,int);
int my_sub(int,int);
int my_mux(int,int);
double my_div(int,int);
#endif
add.c
//add.c
#include "./my_math.h"
int my_add(int a,int b)
{
return a+b;
}
sub.c
//sub.c
#include "./my_math.h"
int my_sub(int a,int b)
{
return a-b;
}
mux.c
//mux.c
#include "./my_math.h"
int my_mux(int a,int b)
{
return a * b;
}
div.c
//div.v
#include "./my_math.h"
#include <stdio.h>
#include <stdlib.h>
double my_div(int a,int b)
{
if(b == 0)
{
printf("b == 0 error\n");
exit(0);
}
return (a*1.0) / b;
}
main.c
//main.c
#include <stdio.h>
#include <stdlib.h>
#include "./my_math.h"
int main()
{
int a = 20;
int b = 3;
printf("a + b = %d\n",my_add(a,b));
printf("a - b = %d\n",my_sub(a,b));
printf("a * b = %d\n",my_mux(a,b));
printf("a / b = %d\n",my_div(a,b));
exit(0);
}
现在对这几个文件进行编译,因为文件较少,所以我们会采取常规的做法
gcc -o main main.c add.c sub.c mux.c div.c
我们发现编译成功,并输出了以下结果:
但如果我们现在修改了其中的一个文件里的代码,比如我们将sub.c里面的文件修改如下:
#include "./my_math.h"
int my_sub(int a,int b)
{ if(a<b)
{
return b-a;
}
return a-b;
}
现在我们如果只编译sub.c的话就会发现出现以下的问题
这个时候如果我们想要程序正确的话就不得不把add.c、mux.c、div.c里面的文件都加以修改,就会很麻烦而且操作不便,如果文件较多的话,我们是没有时间和精力去一一修改每个文件的。哪怕只是改了一个逗号或者分号,我们可能都需要将文件修改。所以我们这个时候就会用到MakeFile文件。
那么如何使用makefile文件呢?
我们先会创建一个MakeFile文件 touch makefile
(大小写都无所谓)。然后在这个文件里去写有关的编译规则。
那如何去写呢?
首先第一行我们需要写的是我们最终生成的可执行文件是谁,以及这个文件需要依赖的是哪些文件。
main:main.o add.o sub.o mux.o div.o
第二行,开头必须是tab键,不可以打四个空格然后是生成这些文件的指令
gcc -o main main.o add.o sub.o mux.o div.o
第三行,这个时候系统会进行自动搜索,会发现这些.o文件都没有。这个时候我们就需要去提供这些.o文件是怎么生成。比如:main.o生成他就得依赖main.c,所以就会是下面的指令
main.o:main.c
第四行,写的就是如何生成main.c(开头必须是tab键)
gcc -c main.c
。。。
其他的类推,所以最后makefile文件里的内容就会是这样
main:main.o add.o sub.o mux.o div.o
gcc -o main main.o add.o sub.omux.o div.o
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
sub.o:sub.c
gcc -c sub.c
mux.o:mux.c
gcc -c mux.c
div.o:div.c
gcc -c div.c
clean
rm *.o
这是一个最简单的makefile文件。在文件的最后一行我们会有这个命令
clean
rm *.o main
意思是将所有的.o文件、main文件都删除。
当我们有了makefile文件后,这个时候就只需要执行一个make命令,就会出现如下图所示:
那么make是如何执行这些指令的呢?
其实这个过程就相当于一个压栈的过程,make会先在当前目录下找makefile文件,找到makefile文件后去解析makefile文件里的这些内容。生成这些内容后我们现在执行./main时会发现跟以前的结果是一样的。
这个时候如果再去执行make的时候就会发现它的优势:它会提示我们文件没有被修改过,已经是最新的了。
以上就是makefile带来的方便和优势。
那么makefiel文件中的clean这行命令是怎样执行的呢?
当我们没有执行make clean这个指令的时候我们会发现此时的文件会有很多的.o文件和main文件。但如果我们执行了make clean这个命令后我们会发现所有的.o文件和main文件都消失了。是因为如果我们执行这个命令的话,make会在makefile文件中找clean这个命令。clean这个命令下是要求我们删除.o文件和main文件。所以我们才会发现所有的.o文件和main文件都消失了。
四、总结
所有整个make的过程可以总结为下:
默认的方式下,也就是我们只输入make命令。那么,
1、make会在当前目录下找名字叫"Makefile" 或"makefile" 的文件。
2、如果找到,它会找文件中的第一个目标文件, 在上面的例子中,他会找到"main"这个文件,并把这个文件作为最终的目标文件。
3、如果main文件不存在,或是main所依赖的后面的.0文件的文件修改时间要比main这个文件新,那么,他就会执行后面所定义的命令来生成main这个文件。
4、如果main所依赖的.o文件也存在,那么make会在当前文件中找目标为.0文件的依赖性,如果找到则再根据那一个规则生成o文件。(这有点像一 个堆栈的过程)
5、 当然,你的C文件和H文件是存在的啦,于是make会生成.0文件,然后再用.。文件生命make的终极任务,也就是执行文件main了