📄 1007.html
字号:
name=header_r2_c1></TD>
<TD background="images/bgline.gif" tppabs="http://www.linuxhero.com/docs/images/bgline.gif" colSpan=5>
<DIV align=right><FONT class=normalfont>当前位置:
<A href="index.html" tppabs="http://www.linuxhero.com/docs/index.html">本站首页</A>
<font color="#FF6699">>></font>
<A href="type14.html" tppabs="http://www.linuxhero.com/docs/type14.html">编程技术</A> | <A href="copyright.html" tppabs="http://www.linuxhero.com/docs/copyright.html">版权说明</A></font></DIV>
</TD>
<TD><IMG height=22 src="images/spacer.gif" tppabs="http://www.linuxhero.com/docs/images/spacer.gif" width=1
border=0></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=10 cellPadding=0 width="100%" bgColor=#ffffff
border=0>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=3 width="100%" border=0>
<TR>
<TD vAlign=top align=middle width="60%">
<TABLE cellSpacing=0 cellPadding=0 width="100%"
background="images/back.gif" tppabs="http://www.linuxhero.com/docs/images/back.gif" border=0>
<TBODY>
<TR>
<TD vAlign=top width="80%">
<DIV align=center>
<FORM action="search.html" tppabs="http://www.linuxhero.com/docs/search.html" method=get>
</FORM>
<TABLE cellSpacing=0 cellPadding=0 width="95%"
border=0><TBODY>
<TR>
<TD background="images/bgi.gif" tppabs="http://www.linuxhero.com/docs/images/bgi.gif"
height=30></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=3 width="95%"
align=center border=0>
<TBODY>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=3 width="100%"
border=0>
<TBODY>
<TR>
<TD vAlign=top>
<p><FONT class=normalfont><B><font color=blue>关于makefile</font></B></FONT><BR><FONT class=smallfont color=#ff9900>2004-04-23 15:18 pm</FONT><BR><FONT class=normalfont>作者:作者<br>来自:Linux知识宝库<br>联系方式:无名<br><br>0) 介绍<br>
本文将首先介绍为什么要将你的C源代码分离成几个合理的独立档案,什么时候需要分,<br>
怎么才能分的好。然后将会告诉你GNU Make怎样使你的编译和连接步骤自动化。对于其它Make<br>
工具的用户来说,虽然在用其它类似工具时要做适当的调整,本文的内容仍然是非常有用的。<br>
如果对你自己的编程工具有怀疑,可以实际地试一试,但请先阅读用户手册。<br>
<br>
1) 多文件项目<br>
1.1 为什么使用它们?<br>
首先,多文件项目的好处在那里呢?<br>
它们看起来把事情弄得复杂无比。又要header文件,又要extern声明,而且如果需要查找<br>
一个文件,你要在更多的文件里搜索。但其实我们有很有力的理由支持我们把一个项目分解成<br>
小块。当你改动一行代码,编译器需要全部重新编译来生成一个新的可执行文件。但如果你的<br>
项目是分开在几个小文件里,当你改动其中一个文件的时候,别的源文件的目标文件(object <br>
files)已经存在,所以没有什么原因去重新编译它们。你所需要做的只是重现编译被改动过的<br>
那个文件,然后重新连接所有的目标文件罢了。在大型的项目中,这意味着从很长的(几分钟<br>
到几小时)重新编译缩短为十几,二十几秒的简单调整。只要通过基本的规划,将一个项目分<br>
解成多个小文件可使你更加容易的找到一段代码。很简单,你根据代码的作用把你的代码分解<br>
到不同的文件里。当你要看一段代码时,你可以准确的知道在那个文件中去寻找它。从很多目<br>
标文件生成一个程序包 (Library)比从一个单一的大目标文件生成要好的多。当然实际上这是<br>
否真是一个优势则是由你所用的系统来决定的。但是当使用 gcc/ld (一个 GNU C 编译/连接<br>
器) 把一个程序包连接到一个程序时,在连接的过程中,它会尝试不去连接没有使用到的部分。<br>
但它每次只能从程序包中把一个完整的目标文件排除在外。因此如果你参考一个程序包中某一<br>
个目标档中任何一个符号的话,那么这个目标文件整个都会被连接进来。要是一个程序包被非<br>
常充分的分解了的话,那么经连接后,得到的可执行文件会比从一个大目标文件组成的程序包<br>
连接得到的文件小得多。又因为你的程序是很模块化的,文件之间的共享部分被减到最少,那<br>
就有很多好处——可以很容易的追踪到臭虫,这些模块经常是可以用在其它的项目里的,同时别<br>
人也可以 更容易的理解你的一段代码是干什么的。当然此外还有许多别的好处…… <br>
<br>
1.2 何时分解你的项目<br>
很明显,把任何东西都分解是不合理的。象“世界,你们好”这样的简单程序根本就不能分,<br>
因为实在也没什么可分的。把用于测试用的小程序分解也是没什么意思的。但一般来说,当分<br>
解项目有助于布局、发展和易读性的时候,我都会采取它。在大多数的情况下,这都是适用的。<br>
(所谓“世界,你们好”,既 'hello world' ,只是一个介绍 一种编程语言时惯用的范例程序,<br>
它会在屏幕上显示一行 'hello world' 。是最简单的程序。)如果你需要开发一个相当大的项<br>
目,在开始前,应该考虑一下你将如何实现它,并且生 成几个文件(用适当的名字)来放你的<br>
代码。当然,在你的项目开发的过程中,你可以 建立新的文件,但如果你这么做的话,说明你<br>
可能改变了当初的想法,你应该想想是否 需要对整体结构也进行相应的调整。对于中型的项目,<br>
你当然也可以采用上述技巧,但你也可以就那么开始输入你的代码, 当你的码多到难以管理的<br>
时候再把它们分解成不同的档案。但以我的经验来说,开始时 在脑子里形成一个大概的方案,<br>
并且尽量遵从它,或在开发过程中,随着程序的需要而 修改,会使开发变得更加容易。<br>
<br>
1.3 怎样分解项目<br>
先说明,这完全是我个人的意见,你可以(也许你真的会?)用别的方式来做。这会触 动<br>
到有关编码风格的问题,而大家从来就没有停止过在这个问题上的争论。 在这里我只是给出我<br>
自己喜欢的做法(同时也给出这么做的原因):<br>
i) 不要用一个 header 文件指向多个源码文件(例外:程序包的header文件)。 用一个<br>
header定义一个源码文件的方式 会更有效,也更容易查寻。否则改变一个源文件的结构(并且<br>
它的header文件)就必须重新编译好几个文件。<br>
ii) 如果可以的话,完全可以用超过一个的 header 文件来指向同 一个源码文件。有时将<br>
不可公开调用的函数原型,类型定义 等等,从它们的C源码文件中分离出来是非常有用的。使<br>
用一个header文件装公开符号,用另一个装私人符号意味着如果 你改变了这个源码文件的内部<br>
结构,你可以只是重新编译它而 不需要重新编译那些使用它的公开 header 文件的其它的源文<br>
件。<br>
iii) 不要在多个 header 文件中重复定义信息。 如果需要, 在其中一个 header 文件里 <br>
#include 另一个,但是不要重复输入相同的 header 信息两次。原因是如果你以后改变了这个<br>
信息,你只需要把它改变一次,不用搜索并改变另外一个重复的信息。<br>
iv) 在每一个源码文件里, #include 那些声明了源码文件中的符 号的所有 header 文件。<br>
这样一来,你在源码文件和 header 文件对某些函数做出的矛盾声明可以比较容易的被编译器发现。<br>
<br>
1.4 对于常见错误的注释<br>
a) 定义符 (Identifier) 在源码文件中的矛盾:在C里,变量和函数的缺 省状态是公用的。<br>
因此,任何C源码档案都可以引用存在于其它源 码档中的通用(global) 函数和通用变量,既使<br>
这个档案没有那个变 量或函数的声明或原型。因此你必须保证在不同的两个档案里不能 用同一<br>
个符号名称,否则会有连接错误或者在编译时会有警告。一种避免这种错误的方法是在公用的符<br>
号前加上跟其所在源文件有 关的前缀。比如:<br>
所有在 gfx.c 里的函数都加上前缀“gfx_”。如果你很小心的分解你的程序,使用有 意义的<br>
函数名称,并且不是过分 使用通用变量,当然这根本就不是问题。 要防止一个符号在它被定义<br>
的源文件以外被看到,可在它的定义前 加上关键字 “static”。这对只在一个档案内部使用,其<br>
它档案都不会用到的简单函数是很有用的。<br>
b) 多次定义的符号: header 档会被逐字的替换到你源文件里 #include 的位置的。 因此,<br>
如果header档被 #include 到一个以上的源文件 里,这个 header 档中所有 的定义就会出现在<br>
每一个有关的源码文件 里。这会使它们里的符号被定义一次以上, 从而出现连接错误(见上)。<br>
解决方法: 不要在 header 档里定义变量。你只需要在 header 档里声明它们然后在 适当的C<br>
源码文件(应该 #include 那个 header 档的那个)里定义它们(一次)。对于初学者来说,定<br>
义和声明是 很容易混淆的。声明的作用是告诉编译器其所声明的符 号应该存在,并且要有所指<br>
定的类型。但是,它并不会使编译器分配贮存空间。 而定 义的做用是要求编译器分配贮存空间。<br>
当做一个声明而不是做 定义的时候,在声明前放一个关键字“extern”。 例如,我们有一个叫<br>
“counter”的变量,如果想让它成为公用的,我们在一个源码程 序(只在一个里面)的开始定义<br>
它:“int counter;”,再在相关的 header 档里声明 它:“extern int counter;”。函数原型里<br>
隐含着 extern 的意思,所以不需顾虑这个问题。<br>
c) 重复定义,重复声明,矛盾类型:<br>
请考虑如果在一个C源码文件中 #include 两个档 a.h 和 b.h, 而 a.h 又 #include 了 b.h <br>
档(原因是 b.h 档定义了一些 a.h 需要的类型),会发生什么事呢?这时该C源码文件<br>
#include 了 b.h 两次。因此每一个在 b.h 中的 #define 都发生了两 次,每一 个声明发生了<br>
两次,等等。理论上,因为它们是完全一样的拷贝, 所以应该 不会有什么问题,但在实际应用<br>
上,这是不符合C的语法 的,可能在编译时出现错误,或至少是警告。 解决的方法是要确定每<br>
一个 header 档在任一个源码文件中只被包 含了一次。我们一 般是用预处理器来达到这个目的<br>
的。当我们进入 每一个 header 档时,我们为这个 header 档 #define一个巨集 指令。只有在<br>
这个巨集指令没有被定义的前提下,我们 才真正使用 该 header 档的主体。在实际应用上,我<br>
们只要简单的把下面一段 码放在 每一个 header 档的开始部分:<br>
#ifndef FILENAME_H<br>
#define FILENAME_H<br>
然后把下面一行码放在最后:<br>
#endif<br>
用header档的档名(大写的)代替上面的 FILENAME_H,用底线 代替档名中的点。有些人喜欢<br>
在 #endif 加上注释来提醒他们这个 #endif 指的是什么。例如:<br>
#endif /* #ifndef FILENAME_H */<br>
我个人没有这个习惯,因为这其实是很明显的。当然这只是各人的 风格不同,无伤大雅。<br>
你只需要在那些有编译错误的 header 档中加入这个技巧,但在所有的header档中都加入也没<br>
什么损失,到底这是个好习惯。<br>
<br>
1.5 重新编译一个多文件项目<br>
清楚的区别编译和连接是很重要的。编译器使用源码文件来产生某种 形式的目标文件<br>
(object files)。在这个过程中,外部的符号参考并 没有被解释或替换。 然后我们使用连接器<br>
来连接这些目标文件和一些 标准的程序包再加你指定的程序包,最后连接生 成一个可执行程序。<br>
在这个阶段,一个目标文件中对别的文件中的符号的参考被解释,并报告不能被解释的参考,一<br>
般是以错误信息的形式报告出来。基本的步骤就应该是,把你的源码文件一个一个的编译成目标<br>
文件的格 式,最后把所 有的目标文件加上需要的程序包连接成一个可执行文件。具体怎么做是<br>
由你的编译器 决定的。这里我只给出 gcc(GNU C 编译 器)的有关命令,这些有可能对你的非<br>
gcc 编译器也适用。<br>
gcc 是一个多目标的工具。它在需要的时候呼叫其它的元件(预处理程序,编译器,组合程<br>
序,连接器)。具体的哪些元件被呼叫取决于 输入文件的类型和你传递给它的开关。 一般来说,<br>
如果你只给它C源码文件,它将预处理,编译,组合所有 的文件,然后把 所得的目标文件连接<br>
成一个可执行文件(一般生成的 文件被命名为 a.out )。你当然可以这么做,但这会破坏很多<br>
我们 把一个项目分解成多个文件所得到的好处。 如果你给它一个 -c 开关,gcc 只把给它的文<br>
件编译成目标文件, 用源码文件的文件 名命名但把其后缀由“.c”或“.cc”变成“.o”。 如果你给<br>
它的是一列目标文件, gcc 会把它们连接成可执行文件, 缺省文件名是a.out 。你可以改变缺<br>
省名,用开 -o 后跟你指定 的文件名。因此,当你改变了一个源码文件后,你需要重新编译它:<br>
'gcc -c filename.c' 然后重新连接你的项目: 'gcc -o exec_filename *.o'。 如果你改变了<br>
一个 header 档, 你需要重新编译所有 #include 过 这个档的源码文件,你可以用<br>
'gcc -c file1.c file2.c file3.c' 然后象上边一样连接。 当然这么做是很繁琐的,幸亏我们<br>
有些工具使这个步骤变得简单。 本文的第二部分就 是介绍其中的一件工具:GNU Make 工具。<br>
(好家伙,现在才开始见真章。您学到点儿东西没?)<br>
<br>
2) GNU Make 工具<br>
2.1 基本 makefile 结构<br>
GNU Make 的主要工作是读进一个文本文件, makefile 。这个文件里主要是有关哪些文件<br>
(‘target’目的文件)是从哪些别的 文件(‘dependencies’依靠文件)中产 生的,用什么命令<br>
来进行 这个产生过程。有了这些信息, make 会检查磁碟上的文件,如果 目的文件的时间戳<br>
(该文件生成或被改动时的时间)比至少它的一 个依靠文件旧的话, make 就执行相应的命令,<br>
以便更新目的文件。 (目的文件不一定是最后的可执行档,它可以是任何一个文件。) <br>
makefile 一般被叫做“makefile”或“Makefile”。当然你可以 在 make 的命令行指 定别的文件<br>
名。如果你不特别指定,它会寻 找“makefile”或“Makefile”,因此使用这两个名字是最简单的。<br>
一个 makefile 主要含有一系列的规则,如下:<br>
例如,考虑以下的 makefile :<br>
=== makefile 开始 ===<br>
myprog : foo.o bar.o<br>
gcc foo.o bar.o -o myprog<br>
foo.o : foo.c foo.h bar.h<br>
gcc -c foo.c -o foo.o<br>
bar.o : bar.c bar.h<br>
gcc -c bar.c -o bar.o<br>
=== makefile 结束 ===<br>
这是一个非常基本的 makefile —— make 从最上面开始,把上 面第一个目的, ‘myprog’,<br>
做为它的主要目标(一个它需要保 证其总是最新的最终目标)。给出的 规则说明只要文件<br>
‘myprog’ 比文件‘foo.o’或‘bar.o’中的任何一个旧,下一行的命令将 会被执行。但是,在<br>
检查文件 foo.o 和 bar.o 的时间戳之前,它会往下查找那些把 foo.o 或 bar.o 做为目标<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -