我们写大型项目时,一般需要很多源文件,源文件会在不同的目录中的文件夹里面包含着,这样我们所有的源文件不会在一个文件中包含,用gcc -o main 所有的.c文件来编译,就很麻烦了,你需要记住所有的.c文件。这时makefile应运而生。
文章目录
一、makefile概念
- 概念: makefile它制定一系列的规则来指定,哪写文件需要先编译,哪写文件需要后编译,那些文件需要重新编译(可以自动检测哪写文件被修改了,修改了进行编译,没修改就不用重新编译了)。和windows上IDE一样。
- 优点: 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.省略指令
熟练之后我们写的时候:
- 就只用先写一行可执行文件需要依赖的.o,再将.o文件单独列出。make和我们约定好了用C编译器“cc”生成[.o]文件的规则,这就是隐含规则。
- 不过它是通过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无法处理,会报错,那么如何处理呢:
- 引入makefile文件中的特殊变量“VPATH”。
- 如果没有这个变量,make就只会再当前的目录中寻找依赖文件和目标文件。
- 如果定义了这个变量,make就会在当前目录找不到的情况下,到指定目录下去找文件。
- 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不能自动清理中间文件,为了让它自动:
- 我们需要引入cleanobj这个可执行文件,把它和可执行文件一起写到all:后面,那么生成可执行文件后就会自动执行它。
- 它没有依赖文件,直接写指令,可执行文件执行后就执行这个指令
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^/。