Makefile 文件
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。