Makefile文件的格式与用法Word文档格式.docx
《Makefile文件的格式与用法Word文档格式.docx》由会员分享,可在线阅读,更多相关《Makefile文件的格式与用法Word文档格式.docx(19页珍藏版)》请在冰豆网上搜索。
1)多文件项目
~~~~~~~~~~~~~~~~~~~~~~
1.1为什么使用它们?
首先,多文件项目的好处在那里呢?
它们看起来把事情弄的复杂无比。
又要header文件,又要extern声明,而且如果需要查找一个文件,你要在更多的文件里搜索。
但其实我们有很有力的理由支持我们把一个项目分解成小块。
当你改动一行代码,编译器需要全部重新编译来生成一个新的可执行文件。
但如果你的项目是分开在几个小文件里,当你改动其中一个文件的时候,别的源文件的目标文件(objectfiles)已经存在,所以没有什么原因去重新编译它们。
你所需要做的只是重现编译被改动过的那个文件,然后重新连接所有的目标文件罢了。
在大型的项目中,这意味着从很长的(几分钟到几小时)重新编译缩短为十几,二十几秒的简单调整。
只要通过基本的规划,将一个项目分解成多个小文件可使你更加容易的找到一段代码。
很简单,你根据代码的作用把你的代码分解到不同的文件里。
当你要看一段代码时,你可以准确的知道在那个文件中去寻找它。
从很多目标文件生成一个程序包(Library)比从一个单一的大目标文件生成要好的多。
当然实际上这是否真是一个优势则是由你所用的系统来决定的。
但是当使用gcc/ld(一个GNUC编译/连接器)把一个程序包连接到一个程序时,在连接的过程中,它会尝试不去连接没有使用到的部分。
但它每次只能从程序包中把一个完整的目标文件排除在外。
因此如果你参考一个程序包中某一个目标档中任何一个符号的话,那么这个目标文件整个都会被连接进来。
要是一个程序包被非常充分的分解了的话,那么经连接后,得到的可执行文件会比从一个大目标文件组成的程序包连接得到的文件小得多。
又因为你的程序是很模块化的,文件之间的共享部分被减到最少,那就有很多好处——可以很容易的追踪到臭虫,这些模块经常是可以用在其它的项目里的,同时别人也可以更容易的理解你的一段代码是干什么的。
当然此外还有许多别的好处……
1.2何时分解你的项目
很明显,把任何东西都分解是不合理的。
象“世界,你们好”这样的简单程序根本就不能分,因为实在也没什么可分的。
把用于测试用的小程序分解也是没什么意思的。
但一般来说,当分解项目有助于布局、发展和易读性的时候,我都会采取它。
在大多数的情况下,这都是适用的。
(所谓“世界,你们好”,既'
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