Makefile文件的格式与用法.docx

上传人:b****5 文档编号:4599955 上传时间:2022-12-07 格式:DOCX 页数:17 大小:30.91KB
下载 相关 举报
Makefile文件的格式与用法.docx_第1页
第1页 / 共17页
Makefile文件的格式与用法.docx_第2页
第2页 / 共17页
Makefile文件的格式与用法.docx_第3页
第3页 / 共17页
Makefile文件的格式与用法.docx_第4页
第4页 / 共17页
Makefile文件的格式与用法.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

Makefile文件的格式与用法.docx

《Makefile文件的格式与用法.docx》由会员分享,可在线阅读,更多相关《Makefile文件的格式与用法.docx(17页珍藏版)》请在冰豆网上搜索。

Makefile文件的格式与用法.docx

Makefile文件的格式与用法

目录

Makefile文件的格式与用法2

0)介绍3

1)多文件项目3

1.1为什么使用它们?

3

1.2何时分解你的项目4

1.3怎样分解项目4

1.4对于常见错误的注释5

1.5重新编译一个多文件项目6

2)GNUMake工具7

2.1基本makefile结构7

2.2编写make规则(Rules)8

2.3Makefile变量9

2.4隐含规则(ImplicitRules)10

2.5假象目的(PhonyTargets)10

2.6函数(Functions)11

2.7一个比较有效的makefile12

2.8一个更好的makefile13

 

Makefile文件的格式与用法

 

编号:

QA002183

建立日期:

1999年12月7日最后修改日期:

1999年12月7日

所属类别:

C/C++-其他方面

beeboo:

操作系统:

win98

编程工具:

vc++,masm6.11

问题:

现在我正在学习编程,有时候要写makefile文件。

我对这种格式文件语法不大清楚,可不可以给我详细介绍一下。

还有在那些网站有这方面的资料可以学习的?

回答:

这是一篇参考。

还有在这能找到更多的资料:

http:

//www.whlug.clinux.org/

GNUmake指南

翻译:

哈少

译者按:

本文是一篇介绍GNUMake的文章,读完后读者应该基本掌握了make的用法。

而make是所有想在Unix(当然也包括Linux)系统上编程的用户必须掌握的工具。

如果你写的程序中没有用到make,则说明你写的程序只是个人的练习程序,不具有任何实用的价值。

也许这么说有点儿偏激,但make实在是应该用在任何稍具规模的程序中的。

希望本文可以为中国的Unix编程初学者提供一点儿有用的资料。

中国的Linux用户除了学会安装红帽子以外,实在应该尝试写一些有用的程序。

个人想法,大家参考。

C-Scene题目#2

多文件项目和GNUMake工具

作者:

乔治富特(GoergeFoot)

电子邮件:

george.foot@merton.ox.ac.uk

Occupation:

StudentatMertonCollege,OxfordUniversity,England

职业:

学生,默尔顿学院,牛津城大学,英格兰

IRC匿名:

gfoot

拒绝承诺:

作者对于任何因此而对任何事物造成的所有损害(你所拥有或不拥有的实际的,抽象的,或者虚拟的)。

所有的损坏都是你自己的责任,而与我无关。

所有权:

“多文件项目”部分属于作者的财产,版权归乔治富特1997年五月至七月。

其它部分属CScene财产,版权CScene1997年,保留所有版权。

本CScene文章的分发,部分或全部,应依照所有其它CScene的文章的条件来处理。

0)介绍

~~~~~~~~~~~~~~~

本文将首先介绍为什么要将你的C源代码分离成几个合理的独立档案,什么时候需要分,怎么才能分的好。

然后将会告诉你GNUMake怎样使你的编译和连接步骤自动化。

对于其它Make工具的用户来说,虽然在用其它类似工具时要做适当的调整,本文的内容仍然是非常有用的。

如果对你自己的编程工具有怀疑,可以实际的试一试,但请先阅读用户手册。

1)多文件项目

~~~~~~~~~~~~~~~~~~~~~~

1.1为什么使用它们?

首先,多文件项目的好处在那里呢?

它们看起来把事情弄的复杂无比。

又要header文件,又要extern声明,而且如果需要查找一个文件,你要在更多的文件里搜索。

但其实我们有很有力的理由支持我们把一个项目分解成小块。

当你改动一行代码,编译器需要全部重新编译来生成一个新的可执行文件。

但如果你的项目是分开在几个小文件里,当你改动其中一个文件的时候,别的源文件的目标文件(objectfiles)已经存在,所以没有什么原因去重新编译它们。

你所需要做的只是重现编译被改动过的那个文件,然后重新连接所有的目标文件罢了。

在大型的项目中,这意味着从很长的(几分钟到几小时)重新编译缩短为十几,二十几秒的简单调整。

只要通过基本的规划,将一个项目分解成多个小文件可使你更加容易的找到一段代码。

很简单,你根据代码的作用把你的代码分解到不同的文件里。

当你要看一段代码时,你可以准确的知道在那个文件中去寻找它。

从很多目标文件生成一个程序包(Library)比从一个单一的大目标文件生成要好的多。

当然实际上这是否真是一个优势则是由你所用的系统来决定的。

但是当使用gcc/ld(一个GNUC编译/连接器)把一个程序包连接到一个程序时,在连接的过程中,它会尝试不去连接没有使用到的部分。

但它每次只能从程序包中把一个完整的目标文件排除在外。

因此如果你参考一个程序包中某一个目标档中任何一个符号的话,那么这个目标文件整个都会被连接进来。

要是一个程序包被非常充分的分解了的话,那么经连接后,得到的可执行文件会比从一个大目标文件组成的程序包连接得到的文件小得多。

又因为你的程序是很模块化的,文件之间的共享部分被减到最少,那就有很多好处——可以很容易的追踪到臭虫,这些模块经常是可以用在其它的项目里的,同时别人也可以更容易的理解你的一段代码是干什么的。

当然此外还有许多别的好处……

1.2何时分解你的项目

很明显,把任何东西都分解是不合理的。

象“世界,你们好”这样的简单程序根本就不能分,因为实在也没什么可分的。

把用于测试用的小程序分解也是没什么意思的。

但一般来说,当分解项目有助于布局、发展和易读性的时候,我都会采取它。

在大多数的情况下,这都是适用的。

(所谓“世界,你们好”,既'helloworld',只是一个介绍一种编程语言时惯用的范例程序,它会在屏幕上显示一行'helloworld'。

是最简单的程序。

如果你需要开发一个相当大的项目,在开始前,应该考虑一下你将如何实现它,并且生成几个文件(用适当的名字)来放你的代码。

当然,在你的项目开发的过程中,你可以建立新的文件,但如果你这么做的话,说明你可能改变了当初的想法,你应该想想是否需要对整体结构也进行相应的调整。

随时会变,如何变

对于中型的项目,你当然也可以采用上述技巧,但你也可以就那么开始输入你的代码,当你的码多到难以管理的时候再把它们分解成不同的档案。

但以我的经验来说,开始时在脑子里形成一个大概的方案,并且尽量遵从它,或在开发过程中,随着程序的需要而修改,会使开发变得更加容易。

1.3怎样分解项目

先说明,这完全是我个人的意见,你可以(也许你真的会?

)用别的方式来做。

这会触动到有关编码风格的问题,而大家从来就没有停止过在这个问题上的争论。

在这里我只是给出我自己喜欢的做法(同时也给出这么做的原因):

i)不要用一个header文件指向多个源码文件(例外:

程序包的header文件)。

用一个header定义一个源码文件的方式会更有效,也更容易查寻。

否则改变一个源文件的结构(并且它的header文件)就必须重新编译好几个文件。

ii)如果可以的话,完全可以用超过一个的header文件来指向同一个源码文件。

有时将不可公开调用的函数原型,类型定义等等,从它们的C源码文件中分离出来是非常有用的。

使用一个header文件装公开符号,用另一个装私人符号,意味着如果你改变了这个源码文件的内部结构,你可以只是重新编译它而不需要重新编译那些使用它的公开header文件的其它的源文件。

iii)不要在多个header文件中重复定义信息。

如果需要,在其中一个header文件里#include另一个,但是不要重复输入相同的header信息两次。

原因是如果你以后改变了这个信息,你只需要把它改变一次,不用搜索并改变另外一个重复的信息。

iv)在每一个源码文件里,#include那些声明了源码文件中的符号的所有header文件。

这样一来,你在源码文件和header文件对某些函数做出的矛盾声明可以比较容易的被编译器发现。

1.4对于常见错误的注释

a)定义符(Identifier)在源码文件中的矛盾:

在C里,变量和函数的缺省状态是公用的。

因此,任何C源码档案都可以引用存在于其它源码档中的通用(global)函数和通用变量,既使这个档案没有那个变量或函数的声明或原型。

因此你必须保证在不同的两个档案里不能用同一个符号名称,否则会有连接错误或者在编译时会有警告。

一种避免这种错误的方法是在公用的符号前加上跟其所在源文件有关的前缀。

比如:

所有在gfx.c里的函数都加上前缀“gfx_”。

如果你很小心的分解你的程序,使用有意义的函数名称,并且不是过分使用通用变量,当然这根本就不是问题。

要防止一个符号在它被定义的源文件以外被看到,可在它的定义前加上关键字“static”。

这对只在一个档案内部使用,其它档案都都不会用到的简单函数是很有用的。

b)多次定义的符号:

header档会被逐字的替换到你源文件里#include的位置的。

因此,如果header档被#include到一个以上的源文件里,这个header档中所有的定义就会出现在每一个有关的源码文件里。

这会使它们里的符号被定义一次以上,从而出现连接错误(见上)。

解决方法:

不要在header档里定义变量。

你只需要在header档里声明它们然后在适当的C源码文件(应该#include那个header档的那个)里定义它们(一次)。

对于初学者来说,定义和声明是很容易混淆的。

声明的作用是告诉编译器其所声明的符号应该存在,并且要有所指定的类型。

但是,它并不会使编译器分配贮存空间。

而定义的做用是要求编译器分配贮存空间。

当做一个声明而不是做定义的时候,在声明前放一个关键字“extern”。

例如,我们有一个叫“counter”的变量,如果想让它成为公用的,我们在一个源码程序(只在一个里面)的开始定义它:

“intcounter;”,再在相关的header档里声明它:

“externintcounter;”。

函数原型里隐含着extern的意思,所以不需顾虑这个问题。

c)重复定义,重复声明,矛盾类型:

请考虑如果在一个C源码文件中#include两个档a.h和b.h,而a.h又#include了b.h档(原因是b.h档定义了一些a.h需要的类型),会发生什么事呢?

这时该C源码文件#include了b.h两次。

因此每一个在b.h中的#define都发生了两次,每一个声明发生了两次,等等。

理论上,因为它们是完全一样的拷贝,所以应该不会有什么问题,但在实际应用上,这是不符合C的语法的,可能在编译时出现错误,或至少是警告。

解决的方法是要确定每一个header档在任一个源码文件中只被包含了一次。

我们一般是用预处理器来达到这个目的的。

当我们进入每一个header档时,我们为这个header档#define一个巨集指令。

只有在这个巨集指令没有被定义的前提下,我们才真正使用该header档的主体。

在实际应用上,我们只要简单的把下面一段码放在每一个header档的开始部分:

#ifndefFILENAME_H

#defineFILENAME_H

然后把下面一行码放在最后:

#endif

//防止头文件被重复引用

用header档的档名(大写的)代替上面的FILENAME_H,用底线代替档名中的点。

有些人喜欢在#endif加上注释来提醒他们这个#endif指的是什么。

例如:

#endif/*#ifndefFILENAME_H*/

我个人没有这个习惯,因为这其实是很明显的。

当然这只是各人的风格不同,无伤大雅。

你只需要在那些有编译错误的header档中加入这个技巧,但在所有的header档中都加入也没什么损失,到底这是个好习惯。

1.5重新编译一个多文件项目

清楚的区别编译和连接是很重要的。

编译器使用源码文件来产生某种形式的目标文件(objectfiles)。

在这个过程中,外部的符号参考并没有被解释或替换。

然后我们使用连接器来连接这些目标文件和一些标准的程序包再加你指定的程序包,最后连接生成一个可执行程序。

在这个阶段,一个目标文件中对别的文件中的符号的参考被解释,并报告不能被解释的参考,一般是以错误信息的形式报告出来。

基本的步骤就应该是,把你的源码文件一个一个的编译成目标文件的格式,最后把所有的目标文件加上需要的程序包连接成一个可执行文件。

具体怎么做是由你的编译器决定的。

这里我只给出gcc(GNUC编译器)的有关命令,这些有可能对你的非gcc编译器也适用。

gcc是一个多目标的工具。

它在需要的时候呼叫其它的元件(预处理程序,编译器,组合程序,连接器)。

具体的哪些元件被呼叫取决于输入文件的类型和你传递给它的开关。

一般来说,如果你只给它C源码文件,它将预处理,编译,组合所有的文件,然后把所得的目标文件连接成一个可执行文件(一般生成的文件被命名为a.out)。

你当然可以这么做,但这会破坏很多我们把一个项目分解成多个文件所得到的好处。

如果你给它一个-c开关,gcc只把给它的文件编译成目标文件,用源码文件的文件名命名但把其后缀由“.c”或“.cc”变成“.o”。

如果你给它的是一列目标文件,gcc会把它们连接成可执行文件,缺省文件名是a.out。

你可以改变缺省名,用开关-o后跟你指定的文件名。

因此,当你改变了一个源码文件后,你需要重新编译它:

'gcc-cfilename.c'然后重新连接你的项目:

'gcc-oexec_filename*.o'。

如果你改变了一个header档,你需要重新编译所有#include过这个档的源码文件,你可以用'gcc-cfile1.cfile2.cfile3.c'然后象上边一样连接。

当然这么做是很繁琐的,幸亏我们有些工具使这个步骤变得简单。

本文的第二部分就是介绍其中的一件工具:

GNUMake工具。

(好家伙,现在才开始见真章。

您学到点儿东西没?

2)GNUMake工具

~~~~~~~~~~~~~~~~

2.1基本makefile结构

GNUMake的主要工作是读进一个文本文件――makefile。

这个文件里主要是有关哪些文件(‘target’目的文件)是从哪些别的文件(‘dependencies’依靠文件)中产生的,用什么命令来进行这个产生过程。

有了这些信息,make会检查磁碟上的文件,如果目的文件的时间戳(该文件生成或被改动时的时间)比至少它的一个依靠文件旧的话,make就执行相应的命令,以便更新目的文件。

(目的文件不一定是最后的可执行档,它可以是任何一个文件。

makefile一般被叫做“makefile”或“Makefile”。

当然你可以在make的命令行指定别的文件名。

如果你不特别指定,它会寻找“makefile”或“Makefile”,因此使用这两个名字是最简单的。

一个makefile主要含有一系列的规则,如下:

:

...

(tab)

(tab)

.

.

.

例如,考虑以下的makefile:

===makefile开始===

myprog:

foo.obar.o

gccfoo.obar.o-omyprog

foo.o:

foo.cfoo.hbar.h

gcc-cfoo.c-ofoo.o

bar.o:

bar.cbar.h

gcc-cbar.c-obar.o

===makefile结束===

这是一个非常基本的makefile——make从最上面开始,把上面第一个目的,‘myprog’,做为它的主要目标(一个它需要保证其总是最新的最终目标)。

给出的规则说明只要文件‘myprog’比文件‘foo.o’或‘bar.o’中的任何一个旧,下一行的命令将会被执行。

但是,在检查文件foo.o和bar.o的时间戳之前,它会往下查找那些把foo.o或bar.o做为目标文件的规则。

它找到的关于foo.o的规则,该文件的依靠文件是foo.c,foo.h和bar.h。

它从下面再找不到生成这些依靠文件的规则,它就开始检查磁碟上这些依靠文件的时间戳。

如果这些文件中任何一个的时间戳比foo.o的新,命令'gcc-ofoo.ofoo.c'将会执行,从而更新文件foo.o。

接下来对文件bar.o做类似的检查,依靠文件在这里是文件bar.c和bar.h。

现在,make回到‘myprog’的规则。

如果刚才两个规则中的任何一个被执行,myprog就需要重建(因为其中一个.o档就会比‘myprog’新),因此连接命令将被执行。

希望到此,你可以看出使用make工具来建立程序的好处——前一章中所有繁琐的检查步骤都由make替你做了:

检查时间戳。

你的源码文件里一个简单改变都会造成那个文件被重新编译(因为.o文件依靠.c文件),进而可执行文件被重新连接(因为.o文件被改变了)。

其实真正的得益是在当你改变一个header档的时候——你不再需要记住那个源码文件依靠它,因为所有的资料都在makefile里。

make会很轻松的替你重新编译所有那些因依靠这个header文件而改变了的源码文件,如有需要,再进行重新连接。

当然,你要确定你在makefile中所写的规则是正确无误的,只列出那些在源码文件中被#include的header档……

2.2编写make规则(Rules)

最明显的(也是最简单的)编写规则的方法是一个一个的查看源码文件,把它们的目标文件做为目的,而C源码文件和被它#include的header档做为依靠文件。

但是你也要把其它被这些header档#include的header档也列为依靠文件,还有那些被包括的文件所包括的文件……然后你会发现要对越来越多的文件进行管理,然后你的头发开始脱落,你的脾气开始变坏,你的脸色变成菜色,你走在路上开始跟电线杆子碰撞,终于你捣毁你的电脑显示器,停止编程。

到低有没有些容易点儿的方法呢?

当然有!

向编译器要!

在编译每一个源码文件的时候,它实在应该知道应该包括什么样的header档。

使用gcc的时候,用-M开关,它会为每一个你给它的C文件输出一个规则,把目标文件做为目的,而这个C文件和所有应该被#include的header文件将做为依靠文件。

注意这个规则会加入所有header文件,包括被角括号(`<',`>')和双引号(`"')所包围的文件。

其实我们可以相当肯定系统header档(比如stdio.h,stdlib.h等等)不会被我们更改,如果你用-MM来代替-M传递给gcc,那些用角括号包围的header档将不会被包括。

(这会节省一些编译时间)

由gcc输出的规则不会含有命令部分;你可以自己写入你的命令或者什么也不写,而让make使用它的隐含的规则(参考下面的2.4节)。

2.3Makefile变量

上面提到makefiles里主要包含一些规则。

它们包含的其它的东西是变量定义。

makefile里的变量就像一个环境变量(environmentvariable)。

事实上,环境变量在make过程中被解释成make的变量。

这些变量是大小写敏感的,一般使用大写字母。

它们可以从几乎任何地方被引用,也可以被用来做很多事情,比如:

i)贮存一个文件名列表。

在上面的例子里,生成可执行文件的规则包含一些目标文件名做为依靠。

在这个规则的命令行里同样的那些文件被输送给gcc做为命令参数。

如果在这里使用一个变数来贮存所有的目标文件名,加入新的目标文件会变的简单而且较不易出错。

ii)贮存可执行文件名。

如果你的项目被用在一个非gcc的系统里,或者如果你想使用一个不同的编译器,你必须将所有使用编译器的地方改成用新的编译器名。

但是如果使用一个变量来代替编译器名,那么你只需要改变一个地方,其它所有地方的命令名就都改变了。

iii)贮存编译器旗标。

假设你想给你所有的编译命令传递一组相同的选项(例如-Wall-O-g);如果你把这组选项存入一个变量,那么你可以把这个变量放在所有呼叫编译器的地方。

而当你要改变选项的时候,你只需在一个地方改变这个变量的内容。

要设定一个变量,你只要在一行的开始写下这个变量的名字,后面跟一个=号,后面跟你要设定的这个变量的值。

以后你要引用这个变量,写一个$符号,后面是围在括号里的变量名。

比如在下面,我们把前面的makefile利用变量重写一遍:

===makefile开始===

OBJS=foo.obar.o

CC=gcc

CFLAGS=-Wall-O-g

myprog:

$(OBJS)

$(CC)$(OBJS)-omyprog

foo.o:

foo.cfoo.hbar.h

$(CC)$(CFLAGS)-cfoo.c-ofoo.o

bar.o:

bar.cbar.h

$(CC)$(CFLAGS)-cbar.c-obar.o

===makefile结束===

还有一些设定好的内部变量,它们根据每一个规则内容定义。

三个比较有用的变量是$@,$<和$^(这些变量不需要括号括住)。

$@扩展成当前规则的目的文件名,$<扩展成依靠列表中的第一个依靠文件,而$^扩展成整个依靠的列表(除掉了里面所有重复的文件名)。

利用这些变量,我们可以把上面的makefile写成:

===makefile开始===

OBJS=foo.obar.o

CC=gcc

CFLAGS=-Wall-O-g

myprog:

$(OBJS)

$(CC)$^-o$@

foo.o:

foo.cfoo.hbar.h

$(CC)$(CFLAGS)-c$<-o$@

bar.o:

bar.cbar.h

$(CC)$(CFLAGS)-c$<-o$@

===makefile结束===

你可以用变量做许多其它的事情,特别是当你把它们和函数混合使用的时候。

如果需要更进一步的了解,请参考GNUMake手册。

('manmake','manmakefile')

2.4隐含规则(ImplicitRules)

请注意,在上面的例子里,几个产生.o文件的命令都是一样的。

都是从.c文件和相关文件里产生.o文件,这是一个标准的步骤。

其实make已经知道怎么做——它有一些叫做隐含规则的内置的规则,这些规则告诉它当你没有给出某些命令的时候,应该怎么办。

如果你把生成foo.o和bar.o的命令从它们的规则中删除,make将会查找它的隐含规则,然后会找到一个适当的命令。

它的命令会使用一些变量,因此你可以按照你的想法来设定它:

它使用变量CC做为编译器(象我们在前面的例子),并且传递变量CFLAGS(给C编译器,C++编译器用CXXFLAGS),CPPFLAGS(C预处理器旗标),TARGET_ARCH(现在不用考虑这个),然后它加入旗标'-c',后面跟变量$<(第一个依靠名),然后是旗标'-o'跟变量$@(目的文件名)。

一个C编译的具体命令将会是:

$(CC)$(CFLAGS)$(CPPFLAGS)$(TARGET_ARCH)-c$<-o$@

当然你可以按照你自己的需要来定义这些变量。

这就是为什么用gcc的-M或-MM开关输出的码可以直接用在一个makefile里。

2.5假象目的(PhonyTargets)

假设你的一个项目最后需要产生两个可执行文件。

你的主要目标是产生两个可执行文件,但这两个文件是相互独立的——如果一个文件需要重建,并不影响另一个。

你可以使用“假象目的”来达到这种效果。

一个假象目的跟一个正常的目的几乎是一样的,只是这个目的文件是不存在的。

因此,make总是会假设它需要被生成,当把它的依赖文件更新后,就会执行它的规则里的命令行。

如果在我们的makefile开始处输入:

all:

exec1exec2

其中exec1和exec2是我们做为目的的两个可执行文件。

make把这个'all'做为它的主要目的,每次执行时都会尝试把'all'更新。

但既然这行规则里没有哪个命令来作用在一个叫'all'的实际文件(事实上all并不会在磁碟上实际产生),所以这个规则并不真的改变'all'的状态。

可既然这个文件并不存在,所以make会尝试

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高中教育 > 高中教育

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1