1. 为什么需要 Makefile 文件

在 Linux 系统中, 一般通过 make 工具编译链接源文件。当然,只有 make 是不能完成编译链接过程的。我们还需要一个 Makefile 文件来告诉编译器编译链接的规则。换句话说,就是 make 工具和 Makefile 文件需要配合使用。

此时,有些小伙伴可能会感到疑惑:为啥需要这两个东西来实现编译链接。
可以使用类似 gcc test.c -o test 不就可以了吗?

那么试想一种复杂的情况:有多个源文件需要编译链接,那该怎么办?
有些小伙伴会说,有多少文件就写多少文件呀。比如:gcc tool.c main.c -o main

当然,这样做也不是完全不可以。但这样做,存在两个缺点:

  • 源文件数量很多时,手动编写编译链接命令非常容易出错
  • 使用上述编译链接命令,编译器需要进行分别编译多个源文件,效率较低

2. Makefile 简单示例

使用 Makefile 文件,可以提高编译器的编译效率,降低错误编译的可能。
下面来看一个简单的例子,来了解 Makefile 的编写规则。

首先给出 test.c 的源代码

#include <stdio.h>

int main()
{
    printf("hello world\n");
    return 0;
}

下面为它编写 Makefile 文件

test: test.c
	gcc test.c -o test

非常简单,就两行。第二行和之前手写的编译命令简直是一模一样。
Makefile 的编写规则如下:

target: dependencies
	command

command 就是具体的编译命令
target 是要生成的目标(.o 文件 或者 可执行文件)
dependencies 是依赖项,就是生成目标依赖哪些文件
注意:command 前需要有一个 Tab (有些编辑器按下 Tab 键会被转换为 4 个空格,这里必须保证是 Tab)

就这个例子来说,target 就是第二行命令中的目标 test;dependencies 就是第二行命令中的 test.c。

下面来看下编译结果,make 命令执行了 Makefile 中的编译命令。
请添加图片描述

3. Makefile 编译多个文件

Makefile 主要还是用于编译链接多个源文件。下面给出一个具体的例子来说下:
首先给出源代码 main.c , tool.c, bar.c。
main.c 实现了求给定数组最大、最小值,并打印的功能。
tool.c 中有求取最大值的函数 find_max。
bar.c 中有求取最小值的函数 find_min。

具体代码如下:

main.c

#include "tool.h"
#include "bar.h"
#include <stdio.h>



int main()
{
	int arr[5] = {1,9,3,8,0};
	int min = find_min(arr, 5);
	int max = find_max(arr, 5);
	printf("min = %d\n", min);
	printf("max = %d\n", max);
	return 0;	
}

tool.h

int find_max(int arr[], int n);

tool.c

#include "tool.h"

int find_max(int arr[], int n)
{
	int m = arr[0];
	for(int i = 0; i < n; i++)
	{
		if(arr[i] > m)
		{
			m = arr[i];
		}
	}
	return m;
}

bar.h

int find_min(int arr[], int n);

bar.c

#include "bar.h"

int find_min(int arr[], int n)
{
	int m = arr[0];
	for(int i = 0; i < n; i++)
	{	
		if(arr[i] < m)
		{
			m = arr[i];
		}
	}
	return m;
}

那么 Makefile 如何对多个文件进行编译呢?
下面来考虑这样一个问题:
如果一个文件发生变化,需要重新编译整个文件吗?
手写编译语句,如 gcc tool.c main.c -o main,就会编译整个文件。
使用 Makefile 文件的话,就可避免修改部分文件需要编译整个文件的情况。

Makefile 文件如下

main: main.c tool.o bar.o
	gcc main.c tool.o bar.o -o main

tool.o: tool.c
	gcc -c tool.c

bar.o: bar.c
	gcc -c bar.c

clean:
	rm *.o main

首先,要从最终生成的可执行文件写起,此处为 main。还是先写 target - main,
dependencies - main.c tool.o bar.o, 换行之后按下 tab 键,再写 command - gcc main.c tool.o bar.o -o main。
然后,开始写依赖项 tool.o 和 bar.o。
依赖中间文件 .o 文件生成最终的可执行文件,可以避免编译整个文件。哪个文件发生了改变,只需编译该文件对应的 .o 文件即可。之后再与其他文件共同生成最终的可执行文件。

最终编译结果如下图所示:
请添加图片描述
从图中可以看出,terminal 中的执行顺序是与 Makefile 中的书写顺序相反的,将 Makefile 中的内容由下至上执行。

小伙伴们,可能还注意到,Makefile 的最后一行有 clean 项。
这个主要是为了发布源代码用的,如果执行 make clean 命令,clean 项中的内容就会被执行。此处就是删除所有 .o 文件 和 可执行文件 main,只保留源代码。

4. Makefile 实用技巧

4.1 变量

编译源代码时,可以使用 gcc,也可以使用 g++。要是我突然想换个编译器编译源代码该怎么办呢?
一个文件一个文件地手动改吗?把所有的 gcc 改成 g++?
有没有什么好办法呢?当然有了。
可以在 Makefile 中使用变量。话不多说,看个例子。

CC = gcc
CFLAGS = -lm -Wall -g

main: main.c tool.o bar.o
	$(CC) $(CFLAGS) main.c tool.o bar.o -o main

tool.o: tool.c
	$(CC) $(CFLAGS) -c tool.c

bar.o: bar.c
	$(CC) $(CFLAGS) -c bar.c

clean:
	rm *.o main

这个 Makefile 文件用变量 CC 保存了 gcc。也可以使用变量保存一些编译选项,如这里的 -lm -Wall -g。最后的编译结果如下图所示:
请添加图片描述

4.2 生成多个可执行文件

这里打算分别生成 main_min 和 main_max 文件。两个文件分别输出所给数组的最小值和最大值。下面是源文件:

main_min.c

#include "tool.h"
#include "bar.h"
#include <stdio.h>

int main()
{
	int arr[5] = {1,9,3,8,0};
	int min = find_min(arr, 5);
	printf("min = %d\n", min);
	return 0;	
}

main_max.c

#include "tool.h"
#include "bar.h"
#include <stdio.h>

int main()
{
	int arr[5] = {1,9,3,8,0};
	int max = find_max(arr, 5);
	printf("max = %d\n", max);
	return 0;	
}

如果 Makefile 按照上面的方式直接写,那么只会生成第一个文件可执行文件。

Makefile

main_max: main_max.c tool.o bar.o
	gcc main_max.c tool.o bar.o -o main_max

main_min: main_min.c tool.o bar.o
	gcc main_min.c tool.o bar.o -o main_min

tool.o: tool.c
	gcc -c tool.c

bar.o: bar.c
	gcc -c bar.c

clean:
	rm *.o main_max main_min

执行结果如下所示,只生成了 main_max。
请添加图片描述

要想生成多个可执行文件,需要加入 all 字段。Makefile 文件如下所示:

all:main_max main_min
main_max: main_max.c tool.o bar.o
	gcc main_max.c tool.o bar.o -o main_max

main_min: main_min.c tool.o bar.o
	gcc main_min.c tool.o bar.o -o main_min

tool.o: tool.c
	gcc -c tool.c

bar.o: bar.c
	gcc -c bar.c

clean:
	rm *.o main_max main_min
main_max: main_max.c tool.o bar.o
	gcc main_max.c tool.o bar.o -o main_max

main_min: main_min.c tool.o bar.o
	gcc main_min.c tool.o bar.o -o main_min

tool.o: tool.c
	gcc -c tool.c

bar.o: bar.c
	gcc -c bar.c

clean:
	rm *.o main_max main_min

执行结果如下所示,生成了多个可执行文件:main_max 和 main_min。
请添加图片描述

参考文献

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐