从GCC到makefile

makefile听起来像是一个粗陋的加工厂,似乎没有任何内涵而言。然而,在你还在试图弄清楚这个家伙是个文件名字还是个命令的名字或者其它什么的,你首先得问自己这么几个问题:为什么要有makefile,它是做什么的?没有makefile,世界会有什么不一样吗?它,makefile,到底是提供了怎样有效的、差异化的、创造性的服务?

在[c/c++]版块的前述文章里,gemfield描述了如何将一个c语言程序编译成为thinkpad赖以运行的可执行文件,我们用的是gcc手工编译。但是,如果有一大堆c程序,之间有相互调用,而且每个c程序都有复杂的依赖关系,那么手工编译就捉襟见肘了,makefile应运而生。首先,makefile是个文件名。惊愕?是的,它就是个文件,当然,它不是个普通的文件。在这个名为makefile的文件里,它用一些字符按照一些语法定义了一些规则,而这些规则告诉GNU make来如何编译那么一组源文件——不论是用c写的.c源文件还是c++写的.cpp文件。

CivilNet BBS的一片相关帖子在描述makefile时说道:或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。gemfield想要表达的正是这个意思。

再回到开始,那么makefile是一个文件,它和make指令到底是如何搭配工作的?在回答这个问题之前,我们有必要先了解makefile的规则。也就是这个文件到底是如何在语法上阐述着一个个关键的编译规则?上文中提到的makefile里的编译规则是它的内涵,它的外在美就表现为“自动化编译”。

对于程序编译来说,它有一些传统。无论是C、C++、还是pascal,首先要把源文件编译成中间代码文件,在Windows下也就是.obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link);编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中只是声明,而定义应该放在C/C++源文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件);链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File.

gemfield将用一个示例来帮助我们。我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。gemfield的规则是:
***************************************************************
1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
***************************************************************

它的意思是说,只要makefile的内涵足够完美,不管源程序有何变动,一个make指令就可以重新产生新的可执行文件。makefile的规则是:

***************************************************************
目标文件A:所依赖的文件B1、B2、B3、B4……
目标文件B: 所依赖的文件C1、C2、C3、C4……

命令 1
命令 2
命令 3
…………(是make需要执行的指令,可以是任意的gcc命令)
***************************************************************

这个makefile规则大意是说,A依赖B,B依照C规则生成A。如果B中有任何文件比A新的话,C将会被执行。这一点正是makefile被这篇文章作传记的原因,也是makefile的灵魂。makefile编写如下:
***************************************************************
GELE : gemfield.o leaflower.o capucivar.o zsxx.o civilnet.o scienchan.o qdzg.o guanxiao.o

<tab>gcc –o GELE gemfield.o leaflower.o capucivar.o zsxx.o civilnet.o scienchan.o qdzg.o guanxiao.o

gemfield.o : gemfield.c abcdefg.h

<tab>gcc –c gemfield.c

leaflower.o : leaflower.c command.h

<tab>gcc –c leaflower.c

capucivar.o : capucivar.c command.h

<tab>gcc –c leaflower.c

zsxx.o : zsxx.c buffer.h

<tab>gcc –c zsxx.c

civilnet.o : civilnet.c buffer.h

<tab>gcc –c civilnet.c

scienchan.o : scienchan.c buffer.h

<tab>gcc –c scienchan.c

qdzg.o : qdzg.c command.h

<tab>gcc –c qdzg.c

guanxiao.o : guanxiao.c abcdefg.h

<tab>gcc –c gaunxiao.c

clean :
rm GELE gemfield.o leaflower.o capucivar.o zsxx.o civilnet.o sciechan.o qdzg.o guanxiao.o
******************************************************************

冒号前面的文件依赖冒号后面的文件,另外注意tab键,它是不可被忽视的。clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。

当makefile按照这些规则编写完成后,上文中说过,要靠make指令来执行。make是GNU的一个工具,是一个解释makefile中指令的工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make(请严重注意:Qt下的qmake是用来生成makefile的,跟这里的情况不一样)

那么make命令又是如何工作的呢?在默认的方式下,也就是我们只输入make命令。那么:

******************************************************************
1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。(先寻找小写的);
2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“GELE”这个文件,并把这个文件作为最终的目标文件;
3、如果GELE文件不存在,或是GELE所依赖的后面的 .o 文件的文件修改时间要比GELE这个文件新,那么,他就会执行后面所定义的命令来生成GELE这个文件;
4、如果GELE所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件;
5、当然,你的源文件和头文件是存在的,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件GELE了。
******************************************************************

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么就不好意思,我就不工作啦。

通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如gemfield.c,那么根据我们的依赖性,我们的目标gemfield.o会被重编译(也就是在这个依性关系后面所定义的命令),于是gemfield.o的文件也是最新的啦,于是gemfield.o的文件修改时间要比GELE要新,所以GELE也会被重新链接了。而如果我们改变了“command.h”,那么,leaflower.o、capucivar.o和qdzg.o都会被重编译,并且,GELE会被重链接。

但是,makefile的内涵还可以更加高效、简洁、方便维护,这就是gemfield在下面要介绍的makefile宏定义了。

********进入makefile新篇章***************************

开头先说一下,在上文中我们在shell下输入make指令后,make会自动到默认目录下寻找名字叫做makefile的文件,然后再执行其中的GNU工具的指令。然而,默认的并非只有makefile一个名字,按优先级排列的话,应该是GNUmakefile、makefile、Makefile。除此之外,还可以make命令自己指定makefile。makefile文件作为一种make工具的描述文档和行动准则,一般需要包含以下内容:

◆ 宏定义
◆ 源文件之间的相互依赖关系
◆ 可执行的命令

Makefile中允许使用简单的宏指代源文件及其相关编译信息,在Linux中也称宏为变量。在引用宏时只需在变量前加$符号,但你不得不注意的是,如果变量名的长度超过一个字符,在引用时就必须加圆括号()。言下之意,如果变量名只有一个字符的话,加不加圆括号是一样的。

gemfield在此列出了一些宏引用:$(gemfield)、$Z、$(Z),当然,后两者是一样的。

我们特别需要注意的是一些宏的预定义变量,在Unix系统中,$*、$@、$?和$<四个特殊宏的值在执行命令的过程中会发生相应的变化,而在GNU make中则定义了更多的预定义变量。宏定义的使用可以使我们脱离那些冗长乏味的编译选项,为编写makefile文件带来很大的方便。下面是一个具体的例子:

*********************************************************
1:# gemfield的makefile
2:
3: OBJ= gemfield.o leaflower.o capucivar.o
4: LIB= -L/usr/local/lib/-lbar
5:
6: gemfield:$(OBJ)
7: <TAB>gcc –o gemfield $(OBJ) $(LIB)
8:
9: install: gemfield
10:<tab>install –m 644 gemfield /usr/bin
11: .PHONY:install
*********************************************************

前面几行变量的声明无须赘述,注意第4行代码指出了一个位于/usr/local/lib下的libbar.so库。第9行代码给出了一个很有意思的目标,install依赖于gemfield但是却并没有试图创建一个名叫install的文件,取而代之的是,它利用了标准的install程序,把gemfield安装在/usr/bin下。但是仅仅有这一行还是不行的,它的不确定性就在于,如果默认目录下同时含有一个名叫install的文件,那么第9行的规则就遗憾的失效了,在使用make指令时,会提示你“install”is up to date(install 是最新的)。

为此,我们使用了一个.PHONY的指示(directive),它会改变make指令的操作,.PHONY告诉make,在这种情况下,install的目标不是一个文件的文件名。这样执行’make install’会无视’install’文件存在与否。已知.phony目标并非是由其它文件生成的实际文件,make会跳过隐含规则搜索。这就是声明.phony目标会改善性能的原因,即,使你并不担心实际文件存在与否。

纵使大多数的linux发行版的make都支持.PHONY,但也许总有例外,这是让人迷惑的一点。gemfield会持续对此进行关注。更详细的描述可以参考:http://www.civilnet.cn/bbs/browse.php?topicno=6648

【备注】:本文属于gemfield的CivilNet Blog(http://gemfield.cn)【程序语言】版块;bug提交至[gemfield@civilnet.cn];资料发布及讨论区:http://civilnet.cn/bbs;转载此文时,请保证包括【备注】在内的文章的完整性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值