【一步步学习编写Makefile】Makefile介绍

本文是学习Makefile的总结与记录,学习自 跟我一起写Makefile 感谢作者与整理者,需要pdf文件请留言,如有错误请及时提出。

在学习Makefile之前,首先介绍一下make命令,make命令是GNU的工程化编译工具,它用于编译大量互相关联的源代码,使用它可以实现项目的工程化管理,提高开发效率。那么对于一个项目,该如何让它按照我们预想的规则去编译链接执行呢?这就要用到我们要学习的Makefile了。Makefile的作用就是在执行make命令的时候指定编译和链接的规则,包括源代码文件之间的链接关系、依赖关系等。它关系到整个项目工程的编译规则:哪些文件需要先编译,哪些要后编译,哪些需要重新编译等复杂的操作。Makefile文件就像shell脚本一样,在其中也可以执行操作系统的命令。

Makefile实现了自动化编译,当然现在有许多的IDE自带了这样的功能,但是,为了提升自己的项目架构能力与工程管理能力,掌握Makefile的编写还是很有必要的。本系列文章学习自陈皓的《跟我一起写Makefile》,使用的是GNU的make,以C/C++源码为基础。本系列文章将不涉及编译器的知识,只介绍基本的编译与链接知识,如要深入了解,请移步我另一系列文章。

程序的编译和链接

对于程序的编译,无论是哪种编译器,首先都需要将源代码文件编译成中间代码(Win下.obj文件,UNIX下.o文件),即Object文件,然后再将大量的Object文件链接在一起进行执行。

在编译时,编译器检查程序代码中语法的正确性,函数以及变量的声明是否正确,以及进行预处理(例如头文件的所在位置以及宏替换等),此步骤只要语法没有问题,编译就不会出错,便可以生成中间目标文件。

在链接时,主要是链接函数与全局变量,链接器不用管函数在哪一个源文件当中,它关心的是函数所在的中间目标文件。大多数情况下,编译生成的中间目标文件比较多,在链接时需要明确地指出中间目标名,这对于编译很不方便,解决方法是给中间目标文件打包生成一个库文件(Win下是.lib,UNIX是.a)。

综上,编译链接的过程是:

  • 首先源文件会讲过编译阶段生成中间目标文件,再由中间目标文件生成执行文件。
  • 编译时编译器只检测程序的语法、函数,变量是否声明以及一些预处理。如果函数只是声明但是未实现则编译器只会警告(不是所有的编译器都是这样),仍然可以生成中间目标文件。
  • 在链接程序时,链接器会在所有的中间目标文件中寻找函数的实现,如果找不到,那就会提示链接错误。

Makefile 介绍

我们已经知道,在我们执行make命令时,是根据Makefile文件定义的规则去编译执行的,所以Makefile的书写就规定了程序的执行方式。我们从一个简单示例开始:
假设我们现在的工程有8个.c文件,3个.h头文件。我们需要写一个Makefile来定义该程序的执行方式:

  • 如果此工程没有被编译过(即第一次编译),那么所有的.c文件都要被编译并链接。
  • 如果此工程的某几个.c文件被修改了,那么我们只编译被修改的.c文件,并链接目标程序。
  • 如果此工程的头文件被改变了,那么需要编译引用了这几个头文件的.c文件,并链接目标程序。
    对于这些要求,这么做是因为如果每次make都去重新编译或者链接那样会特别耗时(尤其是在项目工程大的时候),因此只对做了改动的文件进行重新编译。

写好Makefile,无论怎么修改源程序,都只需要一个make命令搞定。它会自动根据当前的文件修改的情况来确定哪些文件需要重新编译,从而自己编译所需要的文件和链接目标程序。

Makefile的规则

在着手写Makefile之前,我们来看一下Makefile的规则。

target ... : prerequisites ...
        command
        ...
        ...
  • target也就是一个目标文件,可以是Object 文件,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,将在后面叙述。
  • prerequisites就是,要生成那个target所需要的文件或是目标。
  • command也就是make需要执行的命令。(任意的Shell命令)
    这个规则可以这么看,目标文件target的生成需要依赖prerequisites中的一些文件,而target文件的生成规则是在command中定义的。

下面我们就根据介绍的编写规则完成一开始的要求。项目的8个.c文件和.h文件如下:
。。。
先贴上编写好的Makefile文件:

proc : main.o kbd.o command.o display.o \
              insert.o search.o files.o utils.o
    gcc -o proc main.o kbd.o command.o display.o \
                          insert.o search.o files.o utils.o
main.o : main.c defs.h
    gcc -c main.c
kbd.o : kbd.c defs.h command.h
    gcc -c kbd.c
command.o : command.c defs.h command.h
    gcc -c command.c
display.o : display.c defs.h buffer.h
    gcc -c display.c
insert.o : insert.c defs.h buffer.h
    gcc -c insert.c
search.o : search.c defs.h buffer.h
    gcc -c search.c
files.o : files.c defs.h buffer.h command.h
    gcc -c files.c
utils.o : utils.c defs.h
    gcc -c utils.c
clean :
    rm proc main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o

请注意\代表换行。编写好此文件保存为Makefile或者makefile文件即可。然后在该目录下执行make即可。如果需要删除执行文件与所有的目标文件,直接make clean即可。
根据之前介绍的Makefile的规则,看一下上面的Makefile文件都做了什么:

  1. 此Makefile文件中,目标文件包含可执行文件proc和中间得到目标文件*.o
  2. 目标文件依赖于:之后所有文件,可以看到proc程序依赖于8个.o文件的生成,而每个.o文件的生成又依赖于.c文件或者.h文件。依赖关系的意思是:A依赖于B表示,若想生成A,则需要先有B。
  3. 定义了依赖关系,后续的那一行定义了如何生成目标文件的操作系统命令,这里需要注意的是对于缩进一定要以一个Tab键作为开头。

make是如何工作的

通常我们在命令行知识输入一个make命令,那么,当我们实行make时,它是怎么开始工作的呢?

  • make首先会在当前目录下查找名称为“Makefile”或者“makefile”的文件。
  • 找到文件之后,它会继续找文件中注明的第一个目标文件(即上文的proc),并把这个文件作为最终需要生产的目标文件。
  • 如果目标文件(proc)不存在,或者目标文件后的依赖文件(.o文件)的修改时间要比目标文件(proc)新,那么,它继续寻找后面定义的命令。
  • 如果proc依赖的.o文件存在,那么make会在当前文件中寻找目标为.o文件的依赖性。若找到则根据规则继续生成一个头文件。
  • make会根据文件夹内的.c或者.h生成.o文件,然后再用.o文件生成proc可执行程序。
    综上,我们可以看到make是根据依赖性一步步地完成最终文件的生成。若在此过程中遇到错误,如果是依赖文件未找到或者编写规则错误,则make直接报错并终止。如果是定义的命令不对,make并不会报错,即make只会检查文件依赖,不会对命令进行检查。
    从上面的Makefile还可以看到最后一行有一个clean命令,但是它没有依赖关系,只有相关的执行命令。那么在执行make时,它就不会自动执行。如果需要执行clean,则需要指定它的执行:make clean
    如果该工程已经编译过一次了,当对其中某个源文件修改后,只会根据依赖性进行相关文件的重新编译,而不会整个工程都重新编译。

Makefile中使用变量

在上面编写的Makefile中:

proc : main.o kbd.o command.o display.o \
              insert.o search.o files.o utils.o
    gcc -o proc main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o

这些.o文件我们写了两次,加入项目中又新加入了一个.c文件,那么对于我们的Makefile,需要好几处都做改动。这样的话难免有点枯燥,而且项目大的话Makefile也会写的很混乱,不方便阅读。因此,我们需要一个变量来代替我们写的这些文件。
比如声明一个变量objects(其它名字也可以)来表示这一大串.o文件:

object = main.o kbd.o command.o display.o \
              insert.o search.o files.o utils.o

那么就可以使用objects来代表着一系列文件,使用方法如下:

objects = main.o kbd.o command.o display.o \
                 insert.o search.o files.o utils.o
proc : $(objects)
    gcc -o proc $(objects)

是不是看上去清爽一点了。如果有新的文件,只用修改一下objects,给它添加一个文件即可。关于变量的更多内容将在后面的文章中深入介绍。

让make自动推导

make可以识别并且自动推导命令,它识别一个.o文件,就会自动将对应的.c文件加在依赖关系中。并且也会自动推导出相关的编译命令。有了这个特性,我们的Makefile文件就可以写的更加简洁了:

objects = main.o kbd.o command.o display.o \
                 insert.o search.o files.o utils.o
proc : $(objects)
        gcc -o proc $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
        rm proc $(objects)

这种方式称为make的隐晦规则。.PHONY表示clean是一个伪目标文件。对于详细内容将在后续文章中介绍。

另类的Makefile

make可以自动推导命令,这很方便。但是观察我们新的Makefile,有一个地方还是觉得很扎眼,那就是每个.o文件都依赖于很多的.h文件,能不能把这些.h文件综合到一起呢?
看一下这种书写方式:

objects = main.o kbd.o command.o display.o \
                 insert.o search.o files.o utils.o
proc : $(objects)
        gcc -o proc $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
        rm proc $(objects)

这样的编写风格使得Makefile文件更加简洁,但是随之带来的是不能直观地看出某个.o文件依赖了哪些文件。所以并不是很推荐。

清空目标文件的规则

对于编写习惯的养成而言,我们最好在每个Makefile中都写一个清空目标文件的clean命令,这样有利用重新编译。例如:

clean :
        rm proc $(objects)

或者:

.PHONY : clean
clean :
        -rm proc $(objects)

注意,后者的-表示忽略问题,强制执行。而且不要将clean放在文件的开头,因为Makefile默认执行的就是文件开头的target。因此,对于clean,最好将它放在文件最后。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容