[Lab1]五分钟了解Makefile

Makefile 是组织代码编译的文件,它描述了代码被编译的方式。

如果我们在 test 目录下有三个文件,它们分别是

//make.c
#include <make.h>
 int main() {
    myPrintHello();                                                  
    return 0;
}

//func.c
#include <stdio.h>
#include <make.h>
void myPrintHello(void) {
    printf("Hello world!\n");
    return 0;
}

//make.h
void myPrintHello(void);

如果要编译它们,则在该目录下执行

gcc -o hellomake make.c func.c -I. # -o 定义了可执行文件的名字,-I 指定了 .h 文件的寻找目录,. 代表当前目录

即可生成可执行文件 hellomake 。

为了能够更加方便地编译代码,我们引入 make 命令。

当执行 make 时,它会自动在当前目录下寻找名为 makefile(或 Makefile)的文件,并执行其中的内容。所以我们要在 makefile 中来组织我们的代码编译方式。

首先,makefile 的基本格式为

目标(target) : 依赖
[tab]一些命令

“目标”是指生成的目标文件的名字,“依赖”是生成这个目标文件所需要的文件,“一些命令”是生成这个目标文件所需的命令。

举个栗子:

hellomake: make.c func.c
    gcc -o hellomake make.c func.c -I.

假如将makefile写成上述形式,那么当执行 make 后,目录中变会出现一个名为 hellomake 的可执行文件。要注意的是,在这里无论目标文件(target)的名字写成什么(甚至写成fuck),最后生成的可执行文件的名字都是 hellomake,这是因为你所执行的编译命令中所规定的生成的可执行文件名字就是 hellomake。那既然如此,冒号左边的东西有什么用处呢?事实上,它可以让我们一目了然地看出这个步骤的最终生成文件是什么(如果你编写得规范而不是非得将 hellomake 写成 fuck的话)。

如果你明白了这样写的好处之后,那就再看一个栗子:

hellomake: make.c func.c
    gcc -o $@ $^ -I.

现在出现了两个没见过的符号 $@ 和 $^。这两个符号分别代表着第一行内容中,冒号的左边和右边的内容。对比一下第一个栗子,你会发现两者要表达的意思是一模一样的。运行一下,会发现结果也是一样的。

现在,运用这种格式来书写的优势已经很明显了,那么下面还要引入一个可以让我们的 makefile 更加一目了然的东西:宏。看一个栗子:

CC := gcc
CFLAGS = -I$(LDIR)
LDIR := .
OBJ := make.o func.o

hellomake: $(OBJ)
    $(CC) -o $@ $^ $(CFLAGS)

我们将一些关键的东西放在宏中,让它们变得更加易阅读和可维护。比如我们在第一行写 CC:=gcc,让人一目了然我们使用的编译器是 gcc。在这里还出现了一个令人疑惑的地方:在OBJ中出现的文件名的后缀是 .o 而不是 .c,然而我们的目录中并没有 *.o 的文件。事实上,make 在执行 makefile 时,如果没有找到冒号右边的依赖文件,就会根据现有的 *.c 文件编译成 *.o 文件。然而为什么我们要多走一步先编译为 .o 文件呢?首先我们应明确,gcc 在编译源文件生成可执行文件时,先将源文件编译为二进制目标文件(object files),再链接目标文件生成可执行文件。如果我们在编译之后又修改了某一个源文件,当再次编译时,make 会帮我们分辨出修改过的那个源文件并加以编译,并且由于存在之前生成的二进制目标文件,而其他没有修改过的源文件将不会参与这次编译,待编译结束,重新生成可执行文件。这样,由于我们之前多走了一步,便大大减少了编译的时间(在较大的项目中)。

如果想了解更多 makefile 的语法,可以参照官方文档

posted @ 2016-08-13 15:22  Sevenskey  阅读(...)  评论(...编辑  收藏