系列文章目录



前言

当我们在编译文件的时候,如果文件有很多,如果我们只用gcc一条编译一个文件,这样的效率太慢了,而且如果更改了一个文件,就需要全部重新编译一遍,就显得非常的麻烦,于是就有了makefile工具。

一、例子

我们创建一个小工程,其中有main.c、input.c 和 calcu.c 这三个 C 文件和 input.h、calcu.h 这
两个头文件。其中 main.c 是主体,input.c 负责接收从键盘输入的数值,calcu.c 进行任意两个数
相加,其中 main.c 文件内容如下:

//示例代码 3.3.2.1 main.c 文件代码
#include <stdio.h>
#include "input.h"
#include "calcu.h"
int main(int argc, char *argv[]){
	int a, b, num; 
	input_int(&a, &b);
	num = calcu(a, b);
	printf("%d + %d = %d\r\n", a, b, num);
}

input.c 文件内容如下:

//示例代码 3.3.2.2 input.c 文件代码
#include <stdio.h>
#include "input.h"
void input_int(int *a, int *b){
	printf("input two num:");
	scanf("%d %d", a, b);
	printf("\r\n");
}
calcu.c 文件内容如下:
//示例代码 3.3.2.3 calcu.c 文件代码
#include "calcu.h"
int calcu(int a, int b){
	return (a + b);
}

文件 input.h 内容如下:

//示例代码 3.3.2.4 input.h 文件代码
#ifndef _INPUT_H
#define _INPUT_H
void input_int(int *a, int *b);
#endif

文件 calcu.h 内容如下:

//示例代码 3.3.2.5 calcu.h 文件代码
#ifndef _CALCU_H
#define _CALCU_H
int calcu(int a, int b);
#endif

我们使用gcc对它进行编译,在终端输入命令:

gcc main.c calcu.c input.c -o main


图一 使用gcc编译文件

二、使用Makefile编译

在工程目录下创建Makefile文件,名字叫"Makefile",不然后面无法编译。
在Make file种输入代码:

//示例代码 3.3.2.6 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 9 

clean:
	rm *.o
	rm main

前面的空行一定要使用table键,编好以后使用make命令编译工程:(如果无法找到命令,使用sudo apt-get install安装对应的包即可);


图一 使用make命令编译文件

三、Makefile语法规则

1.规则格式

目标…... : 依赖文件集合……
命令 1
命令 2
……

例如:

main : main.o input.o calcu.o
 gcc -o main main.o input.o calcu.o

这条规则的目标是main,依赖文件是main.o,input.o和calcu.o,如果要更新目标main,就必须先更新它的所有依赖文件,,如果依赖文件中的任何一个有更新,那么目标文件也必须更新。
注意:命令列表中的每条命令必须以 TAB 键开始,不能使用空格!

2.Makefile变量

看个栗子:

 main: main.o input.o calcu.o
 	gcc -o main main.o input.o calcu.o

使用变量后:

//示例代码 3.4.2.1 Makefile 变量使用
#Makefile 变量的使用
objects = main.o input.o calcu.o 
main: $(objects)
	gcc -o main $(objects)

赋值符“=”

使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值

赋值符“:=”

不会使用后面定义的变量,只能使用前面已经定义好的

赋值符“?=”

“?=”是一个很有用的赋值符,比如下面这行代码:

curname ?= zuozhongkai

上述代码的意思就是,如果变量 curname 前面没有被赋值,那么此变量就是“zuozhongkai”,
如果前面已经赋过值了,那么就使用前面赋的值

变量追加“+=”

Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进
去,此时就要使用到符号“+=”,比如如下所示代码:

objects = main.o inpiut.o
objects += calcu.o

一开始变量 objects 的值为“main.o input.o”,后面我们给他追加了一个“calcu.o”,因此变
量 objects 变成了“main.o input.o calcu.o”,这个就是变量的追加。

3.Makefile 模式规则

我们可以使用 Makefile 中的模式规则,通过模式规则我们就可以使用一条规则来将所有的.c 文件编译为对应的.o 文件。当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中的“%”值,使用方法如下:

%.o : %.c
	命令

所以上面的例子我们就可以这样改:

objects = main.o input.o calcu.o
main: $(objects) 
	gcc -o main $(objects)  
%.o : %.c 
	#命令

clean: 
	rm *.o
	rm main

4. Makefile 自动化变量

在我们使用模式规则之后如何去写这个命令呢?就要使用自动化变量了,所谓自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中,常用的自动化变量如表1:
表1 自动化变量表
在这里插入图片描述常用的三种: @ 、 @、 @<和$^,我们使用自动化变量来完成“示例代码中的 Makefile,最终的完整代码如下所示:

//示例代码 3.4.4.1 自动化变量
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects) 
%.o : %.c
	gcc -c $<
clean:
	rm *.o
	rm main

5.Makefile 伪目标

Makefile 有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。
使用伪目标主要是为了避免 Makefile 中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的,比如在前面的“示例代码中有如下代码用来完成清理工程的功能:

clean:
	rm *.o
	rm main
上述规则中并没有创建文件 clean 的命令,因此工作目录下永远都不会存在文件 clean,当我们输入“make clean”以后,后面的“rm *.o”和“rm main”总是会执行。可是如果我们“手贱”,在工作目录下创建一个名为“clean”的文件,那就不一样了,当执行“make clean”的时候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的 rm 命令也就不会执行,我们预先设想的清理工程的功能也就无法完成。为了避免这个问题,我们可以将 clean 声明为伪目标,声明方式如下:
.PHONY : clean

完整代码:

//示例代码 3.4.5.1 伪目标
objects = main.o input.o calcu.o
main: $(objects)
	gcc -o main $(objects) 4 
.PHONY : clean 
%.o : %.c 
	gcc -c $< 
clean:
	rm *.o
	rm main

上述代码第 5 行声明 clean 为伪目标,声明 clean 为伪目标以后不管当前目录下是否存在名为“clean”的文件,输入“make clean”的话规则后面的 rm 命令都会执行。

总结

Makefile 的语法有很多,本文只是对最基本最常用的语法作简单的说明,如果想学完整的语法的话,请参考正点原子开发资料中的makefile语法讲解。