📄 1007.html
字号:
文件的规则。它找到的关于 foo.o 的规则,该文件的依靠文件是 foo.c, foo.h 和 bar.h 。 <br>
它从下面再找不到生成这些依靠文件的规则,它就开始检 查磁碟 上这些依靠文件的时间戳。<br>
如果这些文件中任何一个的时间戳比 foo.o 的新, 命令 'gcc -o foo.o foo.c' 将会执行,<br>
从而更新 文件 foo.o。接下来对文件 bar.o 做类似的检查,依靠文件在这里是文件bar.c <br>
和 bar.h 。 现在, make 回到‘myprog’的规则。如果刚才两个规则中的任 何一个被执行, <br>
myprog 就需要重建(因为其中一个 .o 档就会比 ‘myprog’新),因此连接命令将被 执行。<br>
希望到此,你可以看出使用 make 工具来建立程序的好处——前 一章中所有繁琐的检查<br>
步骤都由 make 替你做了:检查时间戳。 你的源码文件里一个简单改变都会造成那个文件<br>
被重新编译(因 为 .o 文件依靠 .c 文件),进而可执行文件被重新连接(因为.o文件被<br>
改变了)。其实真正的得益是在当你改变一个 header 档的时候——你不 再需要记住那个源<br>
码文件依靠它,因为所有的 资料都在 makefile 里。 make 会很轻松的替你重新编译所有<br>
那 些因依靠这个 header 文件而改变了的源码文件,如有需 要,再 进行重新连接。当然,<br>
你要确定你在 makefile 中所写的规则是正确无误的,只 列出那些在源码文件 中被<br>
#include 的 header 档…… <br>
<br>
2.2 编写 make 规则 (Rules)<br>
最明显的(也是最简单的)编写规则的方法是一个一个的查看源码文件,把它们的目标<br>
文件做为目的,而C源码文件和被它 #include 的 header 档做为依靠文件。但是你也要把<br>
其它被这些 header 档 #include 的 header 档也列为依靠文件,还有那些被包括的文件所<br>
包括的文件……然后你会发现要对越来越多的文件进行管理,然后你的头发开始脱落,你的脾<br>
气开始变坏,你的脸 色变成菜色,你走在路上开始跟电线杆子 碰撞,终于你捣毁你的电脑<br>
显示器,停止编程。到低有没有些容易点儿的方法呢?当然有!向编译器要!在编译每一个<br>
源码文件的时候,它实在应 该知道应该包括什么样的 header 档。使用gcc的时候,用 -M<br>
开关,它会为每一个你给它的C文件输出一个规则,把目标文件 做为目的, 而这个C文件<br>
和所有应该被 #include 的 header 文 件将做为依靠文件。注意这个规则会加入所有header<br>
文件,包 括被角括号(`<', `>')和双引号(`"')所包围的文件。其实我们可以 相当肯定系统<br>
header 档(比如 stdio.h, stdlib.h 等等)不会 被我们更改,如果你用 -MM 来代替 -M <br>
传递给 gcc, 那些用角括 号包围的 header 档将不会被包括。(这会节省一些编译时间)<br>
由 gcc 输出的规则不会含有命令部分;你可以自己写入你的命令 或者什么也不写,而让<br>
make 使用它的隐含的规则(参考下面的 2.4 节)。<br>
2.3 Makefile 变量<br>
上面提到 makefiles 里主要包含一些规则。它们包含的其它的东 西是变量定义。 <br>
makefile 里的变量就像一个环境变量(environment variable)。 事实上,环境变量在make<br>
过程中被解释成 make 的变量。这些 变量是大小写敏感的,一般使用大写字母。 它们可以<br>
从几乎任何 地方被引用,也可以被用来做很多事情,比如:<br>
i) 贮存一个文件名列表。在上面的例子里,生成可执行文件的 规则包含一些目标文件<br>
名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc 做为命令参数。如果在<br>
这里使用一个变数来贮存所有的目标文件名,加入新的目标 文件会变的简单而且较不易出错。<br>
ii) 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系 统里,或者如果你想使<br>
用一个不同的编译器,你必须将所 有使用编译器的地方改成用新的编译器名。但是如果使用<br>
一 个变量来代替编译器名,那么你只需要改变一个地方,其 它所有地方的命令名就都改变了。<br>
iii) 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项(例 -Wall -O -g);<br>
如果你把这组选项存 入一个变量,那么你可以把这个变量放在所有 呼叫编译器 的地方。而<br>
当你要改变选项的时候,你只需在一个地方改 变这个变量的内 容。要设定一个变量, 你只<br>
要在一行的开始写下这个变量的名字,后 面跟一个 = 号,后面 跟你要设定的这个变量的值。<br>
以后你要引用 这个变量,写一个 $ 符号,后面是围在括 号里的变量名。比如在 下面,我们<br>
把前面的 makefile 利用变量重写一遍:<br>
=== makefile 开始 ===<br>
OBJS = foo.o bar.o<br>
CC = gcc<br>
CFLAGS = -Wall -O -g<br>
myprog : $(OBJS)<br>
$(CC) $(OBJS) -o myprog<br>
foo.o : foo.c foo.h bar.h<br>
$(CC) $(CFLAGS) -c foo.c -o foo.o<br>
bar.o : bar.c bar.h<br>
$(CC) $(CFLAGS) -c bar.c -o bar.o<br>
=== makefile 结束 ===<br>
还有一些设定好的内部变量,它们根据每一个规则内容定义。三个 比较有用的变量是<br>
$@, $< 和 $^ (这些变量不需要括号括住)。 $@ 扩展成当前规则的目的文件名, $< 扩<br>
展成依靠列表中的第 一个依靠文件,而 $^ 扩展成整个依靠的列表(除掉了里面所有重复<br>
的文件名)。利用这些变量,我们可以把上面的 makefile 写成:<br>
=== makefile 开始 ===<br>
OBJS = foo.o bar.o<br>
CC = gcc<br>
CFLAGS = -Wall -O -g<br>
myprog : $(OBJS)<br>
$(CC) $^ -o $@<br>
foo.o : foo.c foo.h bar.h<br>
$(CC) $(CFLAGS) -c $< -o $@<br>
bar.o : bar.c bar.h<br>
$(CC) $(CFLAGS) -c $< -o $@<br>
=== makefile 结束 ===<br>
你可以用变量做许多其它的事情,特别是当你把它们和函数混合使用的时候。如果需要<br>
更进一步的了解,请参考 GNU Make 手册。('man make', 'man makefile')<br>
<br>
2.4 隐含规则 (Implicit Rules)<br>
请注意,在上面的例子里,几个产生 .o 文件的命令都是一样的。 都是从 .c 文件和<br>
相关文件里产生 .o 文件,这是一个标准的步 骤。其实 make 已经知道怎么做——它 有一些<br>
叫做隐含规则的内 置的规则,这些规则告诉它当你没有给出某些命令的时候, 应该怎么办。<br>
如果你把生成 foo.o 和 bar.o 的命令从它们的规则中删除, make 将会查找它的隐含规则,<br>
然后会找到一个适当的命令。它的命令会 使用一些变量,因此你可以按照你的 想法来设定<br>
它:它使用变量 CC 做为编译器(象我们在前面的例子),并且传递变量 CFLAGS (给 C <br>
编译器,C++ 编译器用 CXXFLAGS ),CPPFLAGS ( C 预 处理器旗 标), TARGET_ARCH <br>
(现在不用考虑这个),然后它加 入旗标 '-c' ,后面跟变量 $< (第一个依靠名),然<br>
后是旗 标 '-o' 跟变量 $@ (目的文件名)。<br>
一个C编译的 具体命令将 会是:$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ <br>
当然你可以按照你自己的需要来定义这些变量。这就是为什么用 gcc 的 -M 或 -MM 开<br>
关输出的码可以直接用在一个 makefile 里。 2.5 假象目的 (Phony Targets) 假设你的一<br>
个项目最后需要产生两个可执行文件。你的主要目标 是产生两个可执行文 件,但这两个文<br>
件是相互独立的——如果一个文件需要重建,并不影响另一个。你可以使用“假象目的”来达到<br>
这种效果。一个假象目的跟一个正常的目的几乎是一样 的, 只是这个目的文件是不存在的。<br>
因此, make 总是会假设它需要 被生成,当把它 的依赖文件更新后,就会执行它的规则里<br>
的命令 行。 如果在我们的 makefile 开始处输入:<br>
all : exec1 exec2<br>
其中 exec1 和 exec2 是我们做为目的的两个可执行文件。 make 把这个 'all' 做为<br>
它的主要目的,每次执行时都会尝试把 'all' 更新。但既然这行规则里没有哪个命令来作<br>
用在一个叫 'all' 的 实际文件(事实上 all 并不会在磁碟上实际产生),所以这个规则<br>
并不真的改变 'all' 的状态。可既然这个文件并不存在,所以 make 会尝试 更新 all 规<br>
则,因此就检查它的依靠 exec1, exec2 是否需要更新,如果需要,就把 它们更新,从而<br>
达到我们的目的。假象目的也可以用来描述一组非预设的动作。例如,你想把所有由make<br>
产生的文件删 除,你可以在 makefile 里设立这样一个规则:<br>
veryclean :<br>
rm *.o<br>
rm myprog<br>
前提是没有其它的规则依靠这个 'veryclean' 目的,它将永远 不会被执行。但是,<br>
如果你明确的使用命令 'make veryclean' , make 会把这个目的做为它的主要目标,执行<br>
那些 rm 命令。如果你的磁碟上存在一个叫 veryclean 文件,会发生什么事?这 时因为在<br>
这个规则里 没有任何依靠文件,所以这个目的文件一定是 最新的了(所有的依靠文件都已<br>
经是最 新的了),所以既使用户明 确命令make重新产生它,也不会有任何事情发生。解决<br>
方法是标 明所有的假象目的(用 .PHONY),这就告诉 make 不用检查它们 是否存在 于磁<br>
碟上,也不用查找任何隐含规则,直接假设指定的目 的需要被更新。在 makefile 里加入<br>
下面这行包含上面规则的规则:<br>
..PHONY : veryclean<br>
就可以了。注意,这是一个特殊的 make 规则,make 知道 .PHONY 是一个特殊目的, <br>
当然你可以在它的依靠里加入你想用的任何假象 目的,而 make 知道它们都是假象目 的。<br>
<br>
2.6 函数 (Functions)<br>
makefile 里的函数跟它的变量很相似——使用的时候,你用一个$符号跟开括号,函数<br>
名,空格后跟一列由逗号分隔的参数,最后 用关括号结束。例如,在 GNU Make 里 有一<br>
个叫 'wildcard' 的函 数,它有一个参数,功能是展开成一列所有符合由其参数 描述的<br>
文 件名,文件间以空格间隔。你可以像下面所示使用这个命令:<br>
SOURCES = $(wildcard *.c)<br>
这行会产生一个所有以 '.c' 结尾的文件的列表,然后存入变量 SOURCES 里。当然<br>
你不需要一定要把结果存入一个变量。<br>
另一个有用的函数是 patsubst ( patten substitude, 匹配替 换的缩写)函数。<br>
它 需要3个参数——第一个是一个需要匹配的 式样,第二个表示用什么来替换它,第三<br>
个是一个需要被处理的 由空格分隔的字列。例如,处理那个经过上面定义后的变量,<br>
OBJS = $(patsubst %.c,%.o,$(SOURCES))<br>
这行将处理所有在 SOURCES 字列中的字(一列文件名),如果它的 结尾是 '.c' ,<br>
就 用 '.o' 把 '.c' 取代。注意这里的 % 符号将匹 配一个或多个字符,而它每次所匹<br>
配的字串叫做一个‘柄’(stem) 。 在第二个参数里, % 被解读成用第一参数所匹配的 那个柄。<br>
<br>
2.7 一个比较有效的 makefile<br>
利用我们现在所学的,我们可以建立一个相当有效的 makefile 。 这个 makefile<br>
可 以完成大部分我们需要的依靠检查,不用做太大 的改变就可直接用在大多数的项目里。<br>
首先我们需要一个基本的 makefile 来建我们的程序。我们可以让 它搜索当前目录,<br>
找到源码文件,并且假设它们都是属于我们的项 目的,放进一个叫 SOURCES 的变量。 <br>
这里如果也包含所有的 *.cc 文件,也许会更保险,因为源码文件可能是 C++ 码的。 <br>
SOURCES = $(wildcard *.c *.cc) 利用 patsubst ,我们可以由源码文件名产生目标文<br>
件名,我们需 要编译出这些目标 文件。如果我们的源码文件既有 .c 文件,也有 .cc 文件,<br>
我们需要使用相嵌的 patsubst 函数呼叫:<br>
OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCES)))<br>
最里面一层 patsubst 的呼叫会对 .cc 文件进行后缀替代,产生的结 果被外层的<br>
patsubst 呼叫处理,进行对 .c 文件后缀的替代。<br>
现在我们可以设立一个规则来建可执行文件:<br>
myprog : $(OBJS)<br>
gcc -o myprog $(OBJS)<br>
进一步的规则不一定需要, gcc 已经知道怎么去生成目标文件 (object files) 。下面我们可以设定产生依靠信息的规则:<br>
depends : $(SOURCES)<br>
gcc -M $(SOURCES) > depends<br>
在这里如果一个叫 'depends' 的文件不存在,或任何一个源码文件 比一个已存在的<br>
depends 文件新,那么一个 depends 文件会被生 成。depends 文件将会含有由 gcc产生<br>
的关于源码文件的规则(注意-M开关)。现在我们要让 make 把这些规则当做 makefile <br>
档 的一部分。这里使用的技巧很像 C 语言中的 #include 系统——我 们要 求 make 把这<br>
个文件 include 到 makefile 里,如下:<br>
include depends<br>
GNU Make 看到这个,检查 'depends' 目的是否更新了,如果没有, 它用我们给它<br>
的命令重新产生 depends 档。然后它会把这组(新) 规则包含进来,继续处理最终目标<br>
'myprog' 。当看到有关 myprog 的规则,它会检查所有的目标文件是否更新——利用 depends<br>
文件 里的规则,当然这些规则现在已经是更新过的了。<br>
这个系统其实效率很低,因为每当一个源码文件被改动,所有的源码 文件都要被预处<br>
理以产生一个新的 'depends' 文件。而且它也不是 100% 的安全,这是因为当一个 header<br>
档被改动,依靠信息并不会 被更新。但就基本工作来说,它也算相当有用的 了。<br>
<br>
2.8 一个更好的 makefile<br>
这是一个我为我大多数项目设计的 makefile 。它应该可以不需要修 改的用在大部分<br>
项目里。我主要把它用在 djgpp 上,那是一个 DOS 版的 gcc 编译器。因此你可以看到执<br>
行的命令名、 'alleg' 程序包、 和 RM -F 变量都反映了这一点。<br>
=== makefile 开始 ===<br>
######################################<br>
#<br>
# Generic makefile<br>
#<br>
# by George Foot<br>
# email: george.foot@merton.ox.ac.uk<br>
#<br>
# Copyright (c) 1997 George Foot<br>
# All rights reserved.<br>
# 保留所有版权<br>
#<br>
# No warranty, no liability;<br>
# you use this at your own risk.<br>
# 没保险,不负责<br>
# 你要用这个,你自己担风险<br>
#<br>
# You are free to modify and<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -