Makefile的简单教程

Makefile的简单教程

概述

什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的集成开发环境 (integrated development environment,IDE)都为你做了这个工作,但我觉得要作一个好的和专 业的程序员,makefile还是要懂。这就好像现在有这么多的HTML编辑器,但如果你想成为一个专业人士, 你还是要了解HTML的标签的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会 写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。

说起makefile,很多人可能不知道是干什么的,当一个工程可能包含很多个文件的时候,编译起来可能会很复杂,这个时候我们可以写一个makefile编译脚本来定义一下编译的规则,然后只需要执行make命令,就会自动的执行编译脚本里面定义好的编译规则来编译项目,来得到希望得到的可执行文件。

在这里我简述一下几种简单的makefile文件的写法,已经可以解决很大一部分问题了。

提起makefile就必须得说一下make,make是一个工具,它可以解释makefile这个脚本,然后根据规则来编译一个项目。makefile定义规则,make来编译它。

关于makefile的更多知识,可以参考一下跟我一起写Makefile

语法规范

大概就长这个样子:

1
2
3
4
target ... : prerequisites ...
    command
    ...
    ...
  • target:你要生成的可执行文件
  • prerequisites:你要生成的可执行文件的依赖
  • command:通过什么命令来生成它

示例

概念说得再多,不如几个例子来的亲切。

以下包含几个例子以及如何写makefile文件来进行编译。

例子一:

现在我们随便写一个代码,用来找出一个数组中的最大值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// main.cpp
#include <bits/stdc++.h>
using namespace std;
int find_max(int arr[], int n)
{
    int maxx = 0;
    for (int i = 0; i < n; i++)
        maxx = max(maxx, arr[i]);
    return maxx;
}
int main()
{
    int arr[5] = {1, 8, 6, 3, 4};
    printf("max is %d.\n", find_max(arr, 5));
    return 0;
}

这个时候,是最简单的情况,我们只需要一句命令:

1
g++ main.cpp -o main

然后使用./main就可以执行了,但是今天我们学习的是makefile,所以要使用makefile应该来怎么写呢。

我们在本目录下新建一个文件makefile,没有后缀名,然后写上:

1
2
main: main.cpp
	g++ main.cpp -o main

这样,就写好了一个最简单的makefile文件,这个时候,我们在终端中输入make,然后make命令就会自动解析makefile这个文件,然后生成一个可执行文件,然后执行,即可

那么我们来解析一个这个文件,首先写下你要生成的目标文件是啥,这里是main,然后写下依赖于哪个文件,这里依赖main.cpp,然后换一行打一个Tab键,输入你要执行的命令,这时候写下make就是自动执行.

例子二:

我们还是要求数组中的最大值,我们把刚才的文件改造一下,改造成几个文件.

1
2
3
4
5
6
7
8
9
//main.cpp
#include "tool.h"
#include <stdio.h>
int main()
{
    int arr[5] = {1, 8, 6, 3, 4};
    printf("max is %d.\n", find_max(arr, 5));
    return 0;
}
1
2
//tool.h
int find_max(int arr[], int n);
1
2
3
4
5
6
7
8
//tool.cpp
int find_max(int arr[], int n)
{
    int maxx = 0;
    for (int i = 0; i < n; i++)
        maxx = maxx > arr[i] ? maxx : arr[i];
    return maxx;
}

这三个文件,main.cpp调用了tool.h中的find_max函数,而这个函数的实现再tool.cpp中。我们也可以用一个命令来编译:

1
g++ main.cpp tool.cpp -o main

然后执行./main同样可以执行,那么如果写成makefile的写法应该怎么写呢.

1
2
3
4
main: main.cpp tool.o
	g++ main.cpp tool.o -o main
tool.o: tool.cpp
	g++ -c tool.cpp

那这些代表什么意思呢,main这个可执行文件的来源是main.cpptool.o,但是其中一个已经有了,那么另一个tool.o怎么来的呢,我们继续在下面写,tool.o怎么来的,他的来源是tool.cpp,生成他的命令是g++ -c tool.cpp,这时候运行结果如下图:

这样也可以打到目的。

例子三:

假设文件特别多呢,我们对上面的例子进行改造:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//main.cpp
#include "foo.h"
#include "bar.h"
#include <stdio.h>
int main()
{
    int arr[5] = {1, 8, 6, 3, 4};
    printf("max is %d.\n", find_max(arr, 5));
    printf("min is %d.\n", find_min(arr, 5));
    return 0;
}
1
2
//foo.h
int find_max(int arr[], int n);
1
2
3
4
5
6
7
8
//foo.cpp
int find_max(int arr[], int n)
{
    int maxx = 0;
    for (int i = 0; i < n; i++)
        maxx = maxx > arr[i] ? maxx : arr[i];
    return maxx;
}
1
2
//bar.h
int find_min(int arr[], int n);
1
2
3
4
5
6
7
8
//bar.cpp
int find_min(int arr[], int n)
{
    int minn = arr[0];
    for (int i = 0; i < n; i++)
        minn = minn < arr[i] ? minn : arr[i];
    return minn;
}

这一次我们有5个文件,怎么编译呢,当然,直接写编译命令是可以的,模仿上面的写法:

1
g++ bar.cpp foo.cpp main.cpp -o main

这样可以编译出来,同样也可以运行。

那么makefile怎么写呢.

1
2
3
4
5
6
7
8
main: main.cpp bar.o foo.o
	g++ main.cpp bar.o foo.o -o main
bar.o:
	g++ -c bar.cpp
foo.o:
	g++ -c foo.cpp
clean:
	rm *.o main

很容易的就可以看出,main依赖谁,先写上,然后把依赖是怎么编译来的也写上,最后就生成了可执行文件。

但是这一次多了一个clean,这是什么意思呢,我们再生成可执行文件的过程中出现了很多.o文件,我们通过清理命令可以把它清理掉,先执行make生成可执行文件,等用完之后,执行make clean就会发现,现在已经回复原状,演示如下:

假设,我们现在要用其他的编译器编译这个项目,一个一个改编译器会很麻烦,所以我们可以类似于定义变量的方式,改的时候只要改一个地方,就可以完成编译,makefile定义变量也很简单,直接写即可,假设我现在要把编译器换成clang,可以这么写:

1
2
3
4
5
6
7
8
9
cc = clang++ 
main: main.cpp bar.o foo.o
	$(cc) main.cpp bar.o foo.o -o main
bar.o:
	$(cc) -c bar.cpp
foo.o:
	$(cc) -c foo.cpp
clean:
	rm *.o main

修改的时候只需要修改一个地方即可,假设我们要加很多不同的编译参数呢,可以同样采用这种定义变量的方式,比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cc = clang++ 
CFLAGS = -O2 -std=c++14 -Wall
main: main.cpp bar.o foo.o
	$(cc) $(CFLAGS) main.cpp bar.o foo.o -o main
bar.o:
	$(cc) $(CFLAGS) -c bar.cpp
foo.o:
	$(cc) $(CFLAGS) -c foo.cpp
clean:
	rm *.o main

例子四:

我们还是根据上面的例子来修改,这次头文件不变,我们定义两个主函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//max_main.cpp
#include "foo.h"
#include "bar.h"
#include <stdio.h>
int main()
{
    int arr[5] = {1, 8, 6, 3, 4};
    printf("max is %d.\n", find_max(arr, 5));
    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//min_main.cpp
#include "foo.h"
#include "bar.h"
#include <stdio.h>
int main()
{
    int arr[5] = {1, 8, 6, 3, 4};
    printf("min is %d.\n", find_min(arr, 5));
    return 0;
}

我们想要达成的目的是,最终编译出来两个可执行文件,那么我们怎么写呢,可以根据上面的例子模仿一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
cc = g++ 
CFLAGS = -O2 -std=c++14 -Wall
max_main: max_main.cpp bar.o foo.o
	$(cc) $(CFLAGS) max_main.cpp bar.o foo.o -o max_main
min_main: min_main.cpp bar.o foo.o
	$(cc) $(CFLAGS) min_main.cpp bar.o foo.o -o min_main
bar.o:
	$(cc) $(CFLAGS) -c bar.cpp
foo.o:
	$(cc) $(CFLAGS) -c foo.cpp
clean:
	rm *.o

这样对不对呢?我们来测试一下

从图上可以发现,为啥编译出来的只有max_main这一个可执行文件呢,另一个去哪了。

makefile在执行的时候只根据第一条语句,第一条语句已经可以生成可执行文件了,那么他就不会执行生成下一个可执行文件,那么我们怎样可以打到我们的需求呢,我们需要进行一小部分的修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
cc = g++ 
CFLAGS = -O2 -std=c++14 -Wall

all:max_main min_main

max_main: max_main.cpp bar.o foo.o
	$(cc) $(CFLAGS) max_main.cpp bar.o foo.o -o max_main

min_main: min_main.cpp bar.o foo.o
	$(cc) $(CFLAGS) min_main.cpp bar.o foo.o -o min_main

bar.o:
	$(cc) $(CFLAGS) -c bar.cpp
foo.o:
	$(cc) $(CFLAGS) -c foo.cpp
clean:
	rm *.o

我们只需要加这一条即可all:max_main min_main,我们告诉他,需要生成两个文件,再试试:

这个时候已经成功了,生成了两个可执行文件。

结语

这是makefile的一些简单的操作,如果要学习更详细的,推荐几个地址:


最后修改于 2018-12-25

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。