五、Makefile/makefile

我们写大型项目时,一般需要很多源文件,源文件会在不同的目录中的文件夹里面包含着,这样我们所有的源文件不会在一个文件中包含,用gcc -o main 所有的.c文件来编译,就很麻烦了,你需要记住所有的.c文件。这时makefile应运而生。

一、makefile概念

  1. 概念: makefile它制定一系列的规则来指定,哪写文件需要先编译,哪写文件需要后编译,那些文件需要重新编译(可以自动检测哪写文件被修改了,修改了进行编译,没修改就不用重新编译了)。和windows上IDE一样。
  2. 优点: makefile带来的好处就是自动化编译,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率,make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数IDE都有这个命令。

二、如何实现makefile

总结makefile文件的实现思想:就是把你需要的目标文件,.o文件,.c文件如何实现的规则(指令)按照格式写进去就可以。

(一)基础的makefile文件(生成单可执行文件)

前提:所有文件均在统一目录下。

1.格式

【】里内容是格式,()是解释。
首先对可执行文件的生成建立规则

第一行【最后要生成的可执行文件名:需要依赖的文件(.o)】
第二行【 (Tab键)生成可执行文件的指令(gcc -o main main.o ……)】

然后对.o文件的生成建立规则:

第三行【.o文件:需要依赖的文件(.c)】
第四行【 (Tab键)生成.o文件的指令(gcc -c main.c)】

如果还有.o文件继续重复3,4行,只需改变文件名即可。
我们最后只需要一个可执行文件,所以中间的.o文件我们不需要,可以把他们删了,这个标识的作用就是这个。

第n行【clean:(一个标识,需要手动调)】
第n+1行【rm *.o main(删除.o,main文件)】

2.示例

我们写四个函数方法文件(add.c,sub.c,mux.c,div.c)一个头文件(my_math.h),一个主函数文件(main.c)。
四个方法分别是加减乘除,内部实现很简单,我们示例除法的,其他类似:

# 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;
}

头文件里面我们声明这四个方法:

#ifndef ___MYMATH_H//防止头文件重复包含
#define __MYMATH_H

int my_add(int,int);
int my_sub(int,int);
int my_mux(int,int);
double my_div(int,int);

#endif

主函数就调用这四个方法:

# include<stdio.h>
# include<stdlib.h>
# include"./my_math.h"

int main()
{
    int a=6;
    int b=2;
    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));
}

多文件用gcc编译为:

gcc -o main main.c add.c div.c mux.c sub.c

一旦任何一个.c文件改变,那么就需要重新全部编译,不能只编译改变的.c文件,这就是很大的弊端。下面我们看一下makefile是怎么处理的:

main:main.o add.o div.o sub.o mux.o   //main生成所需要的.o文件
	gcc -o main main.o add.o div.o sub.o mux.o    //生成main的规则
main.o:main.c   //mian.o文件生成所需要的mian.c文件(程序员写的,所以不用声明它的规则)
	gcc -c main.c
add.o:add.c
	gcc -c add.c
div.o:div.c
	gcc -c div.c
sub.o:sub.c
	gcc -c sub.c
mux.o:mux.c
	gcc -c mux.c
clean:       //需要手动调的
	rm *.o main

这就是基本的makefile,我们make,就会自动编程程序,生成可执行文件:
在这里插入图片描述
这时我们查看一下现在文件夹的目录,会发现存在很多.o文件,这些我们都是不需要的,这个时候我们刚刚写的最后两行clean这个标签的作用就体现出来了。
在这里插入图片描述
**clean的调用:**需要我们手动调用,因为它不是文件而是一个标签,所以make无法生成它的依赖关系和决定它是否要执行,只能通过显示指定这个目标才可以 ,通过make clean//执行clean冒号后的指令。我们执行过后,.o文件就没有了。
在这里插入图片描述
所以当我们多个文件时要考虑makefile,就算你更改了其中一个文件的内容,也只需要make一下就好。

(二) make指令

我们可以通过上面的例子来理解一下make这个命令它干了啥,它是如何解析makefile文件的:
(1) make会在当前目录下找名字为“Makefile"或”makefile"的文件。
(2)如果找到,从文件中的第一个目标文件,上面例子中的main文件,并把这个文件作为最终的目标可执行文件。
(3)如果main不存在,或者main所依赖的后面.o文件的文件修改时间比main文件新,那么它就会执行后面所定义的命令来生成main这个文件。(这个就是makefile自动检测更新的原因)
(4)如果mian所依赖的.o文件也存在,那么make会在当前文件中找到目标文件为.o文件的依赖性,如果找到则根据那一个规则生成.o文件(类似堆栈),就像上面的例子中,main依赖main.o add.o……,它就会先找main.o,找到main.o,发现main.o依赖main.c,然后根据规则生成main.o,其他.o文件类似,直到生成所有.o,然后生成mian。
(5)这样就生成了可执行文件main,可以运行。
这就是make的作用。

(二)基础的makefile文件(生成多个执行文件)

1.格式

我们也会经常碰到生成多个可执行文件,所以学会写生成多个可执行文件也是很必要的,格式和上面一样,就是加all:后面跟可执行文件,后面写依赖,下面不断写.o依赖的.c,规则是啥。
在开头加上:

all:ELF1 ELF2 ……
ELF1:需要依赖的.o文件
ELF2:需要依赖的.o文件
……
.o文件生成的规则
……

2.示例

我们再写个run.c(内容和mian.c差不多,我就调用了一下加减函数),那么现在需要生成run,main两个可执行文件。所以现在的makefile的内容为:

all:main run  //想生成多少就写多少
run:run.o add.o sub.o  
	gcc -o run run.o add.o sub.o
main:main.o add.o div.o sub.o mux.o
	gcc -o main main.o add.o div.o sub.o mux.o
main.o:main.c
	gcc -c main.c
run.o:run.c
	gcc -c run.c
add.o:add.c
	gcc -c add.c
div.o:div.c
	gcc -c div.c
sub.o:sub.c
	gcc -c sub.c
mux.o:mux.c
	gcc -c mux.c
clean:
	rm *.o main

我们再来运行程序:
在这里插入图片描述

(三)进阶的makefile格式

看了最基础的,我们理解makefile文件的原理,那么当我们熟练之后,就可以进行优化和改进了:

1.省略指令

熟练之后我们写的时候:

  1. 就只用先写一行可执行文件需要依赖的.o,再将.o文件单独列出。make和我们约定好了用C编译器“cc”生成[.o]文件的规则,这就是隐含规则。
  2. 不过它是通过cc编译器编译的,如果要使用gcc/g++,需要在前面加cc=gcc,或者cc=g++;
    我们还是上面的例子(单可执行文件的)
CC=gcc  //不写这个前面为cc
main:main.o add.o div.o sub.o mux.o	
main.o:
add.o:
div.o:
sub.o:
mux.o:
clean:
	rm *.o  main

在这里插入图片描述

2.引入变量

但是我们如果有很多.o文件时那么第一行和第二行.o文件写的会累人,这时我们考虑引入变量,变量保存所有.o文件名,这样用到的地方只用写$(变量名),$是格式,类似宏替换。

file=xx.o xxx.o……
$(file)

为了美观,也可以用\将文件分开

file=xx.o \
xxx.o \
……

例子:

CC=gcc
file=main.o add.o div.o sub.o mux.o
main:$(file)	
$(file):

clean:
	rm *.o  main

3.解决子目录问题

上面我们说了所有的.c文件必须在同一目录下,如果不在那么基本make无法处理,会报错,那么如何处理呢:

  1. 引入makefile文件中的特殊变量“VPATH”。
  2. 如果没有这个变量,make就只会再当前的目录中寻找依赖文件和目标文件。
  3. 如果定义了这个变量,make就会在当前目录找不到的情况下,到指定目录下去找文件。
  4. VPATH使用格式:目录用”:“分割,会先搜寻当前目录,再按这个目录寻找。

VPATH=src:./header
先在当前目录,再去src和./header找

例子:
我们把加减乘除.c文件移动到MATH文件夹中,这时我们make就会出错:
在这里插入图片描述
这时我们引入变量VPATH,目录在当前目录和MATH下去找:

CC=gcc
file=main.o add.o div.o sub.o mux.o
VPATH=.:./MATH
main:$(file)	
$(file):

clean:
	rm *.o  main

在这里插入图片描述
运行正确了

4.makefile自动清理中间文件

我们上面写的基本makefile不能自动清理中间文件,为了让它自动:

  1. 我们需要引入cleanobj这个可执行文件,把它和可执行文件一起写到all:后面,那么生成可执行文件后就会自动执行它。
  2. 它没有依赖文件,直接写指令,可执行文件执行后就执行这个指令

rm *.o //删除所有.o文件

例子:

CC=gcc
file=main.o add.o div.o sub.o mux.o
VPATH=.:./MATH
all:main cleanobj //main执行完自动执行cleanobj
main:$(file)	
$(file):

clean: //手动调
	rm *.o  main
cleanobj://自动调
	rm *.o

在这里插入图片描述

中间的过程他都给我们自动删除了。

三、最简版本makefile

在这里插入图片描述
加油哦!^o^/。

猜你喜欢

转载自blog.csdn.net/qq_43411555/article/details/105792521