谨以此文小记Makefile入门学习的小流程,如果拙笔对各位道友的修行有益,鄙人不胜荣幸!
Makefile有啥用?
学习一个技术或者知识,最终目的是为了创造使用价值,不落实地就是单纯炫技,毫无意义。
在Linux环境下进行C语言编译需要再终端中疯狂使用gcc命令对.c文件进行编译,虽然有助于对工程编译流程理解有非常大的帮助,但如果一个工程里面有成百上千个.c文件需要编译,那gcc可能要敲到冒火星子。
所以,“自动挡”编译工具——Makefile诞生!只需要提前将哪些文件需要编译、哪些文件需要重新编译写好,一个make命令就可以帮你完成整个大工程的编译。
Makefile的基本写法(筑基期)
在这里,先对Makefile这个功法的基础口诀(规则)进行介绍:
口诀:[目标文件]:[依赖文件]
target:prere
这里通过一个例子介绍该基本口诀的使用:先写最终目标!
首先提供一个所用程序的第一个代码
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
这是个非常简单的程序,这时候有人会说直接gcc编译不就好了吗?还要费劲写个Makefile。此言差矣,这是举例!
接下来在相同目录下,创建一个Makefile(没有后缀!就是Makefile)写入以下代码:
#第一步:确定最终需要生成的目标文件
#最终需要生成可执行文件main,那生成这个可执行文件就需要依赖main.o
#口诀 目标文件:依赖文件
main:main.o
#具体gcc命令
gcc -o main main.o
#但是原目录中只有一个.c文件,那就要生成对应的.o文件
#同样,要生成的是main.o,生成这个需要依赖main.c
#口诀 目标文件:依赖文件
main.o:main.c
#具体gcc命令
gcc -c main.c
最终,在终端通过make命令执行编译生成可执行文件main,./运行可执行文件就可以看到输出效果。
接下来上一个多.c文件和.h文件的例子,实现一个简单的加法计算:
首先main.c文件:
#include<stdio.h>
#include"input.h"
#include"calcu.h"
int main()
{
int a, b, num;
input_int(&a, &b);
num = calcu(a, b);
printf("%d + %d = %d\r\n",a ,b, num);
return 0;
}
input.c文件:
#include<stdio.h>
#include"input.h"
void input_int(int *a, int *b)
{
printf("please input two num:\n");
scanf("%d%d",a ,b);
printf("\r\n");
}
input.h文件:
#ifndef __INPUT_H
#define __INPUT_H
void input_int(int *a, int *b);
#endif
calcu.c文件:
#include"calcu.h"
int calcu(int a, int b)
{
return a + b;
}
calcu.h文件:
#ifndef __CALCU_H
#define __CALCU_H
int calcu(int a, int b);
#endif
就只有3个.c 文件,按照之前通过gcc命令编译:
gcc main.c calcu.c input.c -o main
看着任务量是不大,但是一旦需要编译的文件变多,一个个手敲文件名,那就需要一个强大的记忆力(记住所有的文件)以及一个快如闪电的手。
同时,正常来说如果改动某个.c文件的话只需要重新编译那一个.c文件即可,但使用gcc来进行编译,所有的文件都需要重新来过,文件数量少还行,如果是个Linux的大工程,那重新编译所有文件需要花费的时间成本就非常高。
接下来,同样在同一个文件目录下,创建一个Makefile文件。
内容如下:
#第一步:确认最终生成可执行文件main
#生成最终文件需要依靠main.o input.o calcu.o
main:main.o input.o calcu.o
#具体gcc命令
gcc -o main main.o input.o calcu.o
#第二步:将生成最终文件索要的依赖文件生成
main.o:main.c
gcc -c main.c
input.o:input.c
gcc -c input.c
calcu.o:calcu.c
gcc -c calcu.c
#这里是伪目标,即不需要生成目标文件,只需要执行下面的命令即可
#方便清除对应文件的小功能,喜欢就写,不然一个个手删麻烦
.PHONY:
clean:
rm -f *.o
此时!又有人要说了,你不也是一个个手敲需要编译的文件吗?和gcc直接编译比还更麻烦!
首先后续结丹了会有更高级的口诀来减少自己手输的麻烦,其次在上面Makefile中的第二步,如果是已经编译过但是再次修改了某个.c文件,那重新make进行编译就会只编译修改过的那个文件,并不会将所有文件全部重新编译。
接下来进行演示:
第一次编译,所有文件全部编译生成对应目标文件:
编译后修改input.c文件(只是将换行符换了个位置写)。
修改前:
修改后:
再次编译,只有修改过的input.c文件被重新编译了:
Makefile升级写法——变量(结丹期)
接下来给各位道友介绍结丹期的新法器——变量!
众所周知任何语言的高明之处在于可以通过变量可以实现以不变(变量)应万变(变量存放的内容),Makefile也不例外。
Makefile中的变量定义和使用远比C语言来的简单,下面就是示例:
#变量示例
name = testname
curname = $(name)
name = testname1
print:
@echo curname:$(curname)
#最终输出内容为:curname:testname1
可以看到直接输入变量名就可以直接进行赋值,不需要定义变量的类型。同时引用变量只需要通过$(),在括号中输入对应的变量名即可使用。
接下来交代一下Makefile中变量需要用到的各种“=”的含义
- = :替换 即每次使用=对变量进行赋值都会替换原有的内容
- := :恒等于 可以理解为C语言中的通过#define 定义的常量,一经确定不再更改
#示例
name = testname
#使用:=后curname中的值就不再改变
curname := $(name)
name = testname1
print:
@echo curname:$(curname)
#最终输出内容为:curname:testname
- +=:拼接 直接将+=后面的内容拼接到变量原有的内容后
#示例
name = testname
name += testname1
print:
@echo curname:$(name)
#最终输出内容为:curname:testname testname1
Makefile再升级写法(元婴和化神期)
现在,我们对上述加法功能的Makefile进行改写
它原来的样子:
main:main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main.o:main.c
gcc -c main.c
input.o:input.c
gcc -c input.c
calcu.o:calcu.c
gcc -c calcu.c
#这里是伪目标,即不需要生成目标文件,只需要执行下面的命令即可
.PHONY:
clean:
rm -f *.o
改完后的样子:
#目标变量
tar=main
#依赖变量
obj=main.o input.o calcu.o
$(tar):$(obj)
gcc -o $(tar) $(obj)
main.o:main.c
gcc -c main.c
input.o:input.c
gcc -c input.c
calcu.o:calcu.c
gcc -c calcu.c
#这里是伪目标,即不需要生成目标文件,只需要执行下面的命令即可
.PHONY:
clean:
rm -f *.o
这样后续工程中添加新的文件就只要修改目标变量和依赖变量就可以。但是,只用变量还是没有解决大工程编译文件量大的问题!下面那部分还是要一个.c文件编写一个对应的指令,还是没有彻底解放双手解决大工程需要编译文件多,手敲累麻的问题。
因此在这里需要依靠化神期的口诀——模式规则以及自动化变量
首先介绍一下模式规则,又称隐含规则,某种意义上来说就是“通配符”,以现在需要修改的Makefile为例,最主要的是将每个.c文件编译生成.o目标文件,理论上那只要将对应的.c文件都找到并挨个对其生成目标文件即可完成编译。
隐含规则的内容:
- %.c %.o 含义:任意的.c或是.o
- *.c *.o 含义:所有的.c或是.o
通过%.o以及%.c就不需要手动像上面一样有一个.c文件就写一个 xx.o:xx.c了,利用隐含规则,直接一句即可:
main.o:main.c
gcc -c main.c -o main.o
input.o:input.c
gcc -c input.c -o input.o
calcu.o:calcu.c
gcc -c calcu.c -o calcu.o
#可以直接替换为下面的,即来任意的.c我都对应生成一个.o
#这里使用%是为了保证两边依赖和目标是一致的
%.o:%.c
gcc -c %.c -o %.o
这样还没完,直接这样写make是会出错的。接下来介绍自动化变量。
常用的自动化变量:
- $@:所有的目标文件
- $^:所有的依赖文件,使用空格分开,如果在依赖文件中有多个重复的文件,去除重复的依赖文件,只保留一份
- $ <:依赖文件的第一个文件,如果依赖文件是以模式(即“%”)定义的,那么“$<”就是符合模式的一系列的文件集合
还有其他的自动化变量,如$ %、$ +、$?等等,说实话没整明白,等后面琢磨透了再和各位分享
那就可以进行更进一步的修改:
obj=main.o input.o calcu.o
tar=main
$(tar):$(obj)
gcc -o $(tar) $(obj)
%.o:%.c
#这里依赖文件是.c 需要生成的是.o,在这里使用$<和$^效果是一样的
gcc -c $< -o $@
#这里是伪目标,即不需要生成目标文件,只需要执行下面的命令即可
.PHONY:
clean:
rm -f *.o
由此一来,整个Makefile文件的架构就出来了,只需要确定好生成最终可执行文件的依赖文件是什么即可,后面都是固定好的。
但距离完美的Makefile还差很多,后面还有条件判断、函数等更高级功法还未参悟,同时以现在的例子暂时也用不上更加高深的功法z因此,后续的境界需要各位入门继续钻研。
最后恭祝各位道友Makefile境界早日圆满,飞升高级语言的世界!