perlcompile - 关于 Perl 编译器和翻译器的介绍 Perl 一直是有一个编译器的：你的源文件会被编译成一种内部格式（一种语法分析树），并且在运行前还会被优化。从5.005版本起，Perl 在发行时就带有一个模块可以检查优化过的语法分析树（该模块称作B模块("B")），它被用来编写许多有用的功能，包括一个可以将你的Perl转成C源代码的模块，这样再编译后就可以得到一个可执行的文件了。 "B" 模块提供了访问语法分析树的方法, 其它的一些模块（“后端”）则对这个树进行操作。一些把它（语法树）以字节码的形式输出，还有以C源代码形式的输出的，后者以半可读的文本形式输出的。另一些遍历整棵语法树以建立一个关于所使用的子程序，格式及变量的交叉引用表。还有另外一些检查你的代码，看看有没有模棱两可的构造。另一些则重新将语法树导出成Perl代码，可以起代码美化或是消除混乱的代码的作用。 因为 "B" 模块的最初目的是提供一种能将Perl程序转为对应C代码的方法，接着就能把它变成可执行文件了，所以 "B" 模块和它的那些后端模块就被认为是“编译器”了，即使它们实际上没有做任何编译方面的事。这个编译器的各个部分精确的说应该是个“翻译器”，或者一个“检视器”，但是用Perl的人们想要一个“编译选项”而不是一个叫做“检视器”的小玩艺。你能怎么办呢？ 这篇文章的主要内容是讲Perl编译器的用法：它包含的模块，怎样使用那些最重要的后端模块，它们有什么问题，如何让它们工作。

Layout 布局
编译器的后端放在 "B::" 里面，而前端（就是你，编译器的使用者，有时候要与之交互的）是 O 模块。一些后端（如 "B::C")）提供了一些程序（如perlcc）来隐藏模块的复杂性。 这里是一些值得知道的重要后端，并附有它们目前的状态，用0到10的整数表示。（状态0表示目前该部分功能只是有一个框架，还没有实现；状态10则表示如果还有Bug的话，我们会感到很奇怪的）： 将语法树存成机器相关的格式，可供BtyeLoader模块可以在以后重新装入。状态：5（一些部分可以工作，一些不可以，还有一些还没有测试） 创建C代码文件，其中包括了重建语法树和恢复解释器的代码。状态：6（许多情况下可以正常工作，包括使用了Tk的程序）。 按照语法树中运行期代码的路径创建C代码文件。这是最像 Perl - C 翻译器的一个，但是它生成的代码几乎是不能看懂的，因为它把语法树翻译成了一个巨大的switch结构来操作Perl中的结构。最终的目的是在perl程序中给出足够的类型信息后，可以将 perl 数据结构的操作转换为 c 级别的数据结构，对 int 和 float 的操作。状态：5 (有些可以工作，包括不复杂的 Tk 示例). 当发现你的代码中有模棱两可的构造时会发出警告。状态：6（许多情况下可以正常工作，仅仅在很少数的领域内它会停止工作）。 重新生成Perl代码，试着把代码用一致的格式写出来。状态：8（它工作得很好，只是会略去一些晦涩难懂的部分）。 生成关于申明和关于变量以及子程序的使用情况的报告。状态：8（它工作得很好，只是仍有一点延迟方面的bugs）。 接下来的部分介绍怎样使用各种各样的编译器后端。介绍的顺序按照后端的成熟程度排列，所以最为稳定的，经过了验证的后端会最先介绍，还在试验中和没有完成的后端就放到后面描述了。 O模块默认让 -c 开关有效，这防止Perl在编译完代码后运行程序。这也是为什么所有的后端在产生任何输出前都会打印一句： myperlprogram syntax OK

The Cross Referencing Back End 交叉引用后端
交叉引用后端（B::Xref）生成一个关于你的程序的报表，把各个申明以及子程序，变量（包括格式）的使用情况存入文件中去。举例来说，这有一段摘自对pod2man程序分析后生成的报表（该程序是Perl自带的一个例程）： Subroutine clear_noremap Package (lexical) $ready_to_print i1069, 1079 Package main $& 1086 $. 1086 $0 1086 $1 1087 $2 1085, 1085 $3 1085, 1085 $ARGV 1086 %HTML_Escapes 1085, 1085 这里展示了"clear_noremap" 子程序中变量的使用情况。就像变量 $ready_to_print 是 my() (词法) 的一个变量，在第1069行被引入( 原文用的词是introduced，也就是在 my() 中第一次被定义的意思 )，然后在第1079行该变量被使用了。从主包（main package）中来的变量 $& 又在第1086行被使用, 等等。 行号前面可能会有一个字母作为前缀，它们的意思是： 变量首次被引入 (在my()中申明) 。 子程序或者方法的引用。 定义的子程序。 定义的格式。 交叉引用中最为有用的选项就是把报表存入不同的文件，例如要把关于 myperlprogram 的报表存入文件 report 中： $ perl -MO=Xref,-oreport myperlprogram

The Decompiling Back End 反编译后端
反编译后端将把你的Perl语法树重新变成源代码。生成的源代码会按照某种格式组织，所以这个后端可以用来消除代码中的混乱部分。此后端的基本使用方法如下： $ perl -MO=Deparse myperlprogram 你也许马上会发现Perl并不知道如何给你的代码分段。你要自己手动添入新行来把这大断的代码分开。然而现在，让我们看看代码只有一行时情况怎样，这个后端会做些什么： $ perl -MO=Deparse -e '$op=shift||die "usage: $0 code [...]";chomp(@ARGV=<>)unless@ARGV; for(@ARGV){$was=$_;eval$op; die$@ if$@; rename$was,$_ unless$was eq $_}' -e syntax OK $op = shift @ARGV || die("usage: $0 code [...]"); chomp(@ARGV = <ARGV>) unless @ARGV; foreach $_ (@ARGV) { $was = $_; eval $op; die $@ if $@; rename $was, $_ unless $was eq $_; } 这个后端也有几条选项控制生成的代码，举例说，你可以把缩进的尺寸设在4（最大）到2之间： $ perl -MO=Deparse,-si2 myperlprogram -p 开关控制在常常可以不加圆括号的地方加上它们： $ perl -MO=Deparse -e 'print "Hello, world\n"' -e syntax OK print "Hello, world\n"; $ perl -MO=Deparse,-p -e 'print "Hello, world\n"' -e syntax OK print("Hello, world\n"); 要知道更多，请参考 B::Deparse

Lint 后端
lint 后端 （B::Lint） 检察程序中不好的程序风格。一个程序认为的不好风格可能对另外一个程序员来说是用起来很有效的工具，所以有选项让你设定哪些东东将会受到检查。 要运行一个风格检查器检察你的代码： $ perl -MO=Lint myperlprogram 要取消对上下文和没有定义的子程序的检查： $ perl -MO=Lint,-context,-undefined-subs myperlprogram 要知道更多的选项信息，请看 B::Lint

The Simple C Back End 简化的C后端
这个模块用来把你的Perl程序的内部编译状态存储到一个C代码文件中去，而生成的C代码就可以被特定平台上的C编译器转换成一个可执行文件了。最后的程序还会和Perl解释器的库文件静态链接起来，所以它不会节省你的磁盘空间（除非你的Perl是用共享的库文件创建的）或是程序大小，然而，另一方面，程序启动起来会快一些。 "perlcc" 工具缺省是生成以下的可执行文件。 perlcc myperlprogram.pl

The Bytecode Back End 字节码后端
这个模块只有在你能够找到一种方法来装入并运行它生成的字节码时才会显得有用。ByteLoader模块提供了这项功能。 要把Perl转换成可执行的字节码，你可以使用 "perlcc" 的 "-B" 开关: perlcc -B myperlprogram.pl 字节码是和机器类型无关的，所以一旦你编译了一个模块或是程序，它就可以像Perl源代码一样具有可移植性。（假设那个模块或者程序的使用者有一个足够新的Perl解释器来对字节码进行解码） 有一些选项用来控制要生成的字节码的性质和关于优化方面的参数，要知道这些选项的详细情况，请参考 B::Bytecode

The Optimized C Back End 优化的C后端
优化的C后端按照语法树中运行期代码的路径将你的Perl程序转换成等效的(但是被优化了的)C代码文件。这个C程序会直接对Perl的数据结构进行操作，而且也会链接Perl的解释器的库文件，以支持 eval(), "s///e", "require" 等等。 "perlcc" 工具使用 -O 开关生成这种可执行文件。要编译一个Perl程序（以".pl" 或者".p" 结尾）： perlcc -O myperlprogram.pl 从Perl模块创建一个共享库文件（以 ".pm" 结尾）： perlcc -O Myperlmodule.pm 知道更多，请参考 perlcc 和 B::CC. 这个模块是一个自省的（introspective，用Java的术语说就是“reflective”）模块，允许Perl程序审视自己的内部。后端模块都是通过这个模块来访问语法分析树的。而你，后端模块的用户，就不用和B模块打交道了。 这个模块是编译器的那些后端的前端，一般像这样进行调用： $ perl -MO=Deparse myperlprogram 这与在这个Perl程序中使用 "use O 'Deparse'" 相同。 这个模块被 B::Assembler 模块使用，而 B::Assembler 又接着被 B::Bytecode 模块使用，B::Bytecode中有一个字节码形式存放的语法分析树以便以后装入。B::Asmdata自己并不算是一个后端，也许说它是后端的一个组件比较好。 这个模块可以将语法树转为适合存储和恢复的数据形式。它本身不是一个后端，但是算是某个后端的一个组件。 assemble 程序用它来生成字节码。 这个模块被 B::CC 后端使用。它被用来运行“基本块”。一个基本块就是一段从头到尾的操作，中间是不可能停下来或出现分支的。 这个模块可以由程序的语法树生成字节码。生成的字节码会被写入到文件中，以后还能被重新恢复成语法树。总的目标就是为了只进行一次费时的程序编译工作，然后把解释器的状态存入文件中，运行程序时再把状态从文件中恢复。 具体的用法请参考 "The Bytecode Back End" 。 这个模块按照语法树和其他一些解释器的内部数据结构生成C代码。然后你再编译生成的C代码，就可以得到一个可执行文件了。运行时这个可执行文件会恢复解释器和内部的数据结构来转动程序。要知道细节请参考 "The Simple C Back End"。 这个模块按照你程序中的操作生成C代码。不像 B::C 模块只是把解释和它的状态存入C程序中， B::CC 模块生成的是不包含解释器的C 程序，所以用 B::CC 翻译的C 程序运行速度比一般的解释执行的程序速度要快，具体用法请参考 "The Optimized C Back End" 。 这个模块输出一个简洁的 (但是完整的) Perl 分析树。它的输出比 B::Terse 或者 B::Debug 的结果更容易定制 (并且也可以模仿它们)。这个模块对书写自己的后端，或者学习 Perl 实现的人有用。它对一般的程序员没有用处。 这个模块把Perl语法分析树非常详细地输出到标准输出上去。这对正在编写自己的后端程序，或正在深入Perl内部机制的人们来说是非常有用的。对普通程序员来说则没什么用。 这个模块将编译了的语法树反向分析得出Perl源代码，这在调试或是反编译他人代码的时候会是非常有用的。另外让它为你自己的代码做一些美化工作也是可以的。要知道细节请参考 "The Decompiling Back End"。 这个模块把字节码恢复成语法树，它本身不是一个后端，而是某个后端的一个组件。它会被和字节码在一起的 disassemble 程序使用。 这个模块审视你的代码编译后的格式，并且找到那些容易让人皱眉，却又不至于引起警告的地方。举例来说，使用一个标量内容（scalar context）的数组，而不显式地申明成 "scalar(@array)" 。这种情况是会被 Lint 标示出来的。要知道细节请参考 "The Lint Back End"。 这个模块打印出 my() 中的变量在函数或是文件中的使用情况，以得到一份关于 my() 中的变量在定义于文件 myperlprogram 中的子程序 mysub() 中的使用情况的列表： $ perl -MO=Showlex,mysub myperlprogram 要得到一份关于 my() 中的变量在文件myperlprogram中的使用情况的列表： $ perl -MO=Showlex myperlprogram [BROKEN] 这个模块被 B::CC 模块调用。它本身不是后端，但是是某个后端的一个组件。 这个模块被 perlcc 程序调用，而perlcc可以把一个模块编译成可执行文件。B::Stash 把程序使用的符号表打印出来，并被用来阻止 B::CC 为 B::* 或是 O 模块生成C 代码。它本身不是后端，但是是某个后端的一个组件。 这个模块用来打印语法树的内容，但是信息不会有B::Debug打印的那么多。对比来说，"print "Hello, world."" 会让 B::Debug 产生96行输出， 但是 B::Terse只会有6行。 这个模块对正在编写自己的后端程序，或正在深入Perl内部机制的人们来说是非常有用的。对普通程序员来说则没什么用。 这个模块打印一个报表列出在程序中哪里定义和使用了哪些变量，子程序或格式，报表还会列出程序装入的模块。要知道详细的使用方法，请参考 "The Cross Referencing Back End" 。 简单 C 后端目前只保存以字符和数字命名的类型说明 优化的 C 后端会为一些不该为之输出的模块（比如说 DirHandle）输出代码。而且它不太可能正确地处理正在执行的子程序外部的goto语句（goto &sub is OK）。目前 "goto LABEL" 语句在这个后端中完全不会工作。他还会生成让C 编译器头痛无比的巨大的初始化函数。如果把这个初始化函数分割开是能得到比目前更好的效果的。另外的问题包括：处理无符号的数学问题时不能正确工作；一些操作码如果按照默认的操作码机制处理也会有非正常的结果。 BEGIN{} 块会在编译你的代码的时候被执行。所有的在BEGIN{} 中初始化的外部状态，如打开的文件，初始的数据库连结等等，会有不正确的表现。为了解决这个问题，Perl中又提供了一个 INIT{} 块来对应程序编译之后，正式运行之前要执行的那段代码。执行的顺序是：BEGIN{}, (后端编译程序可能这时会保存状态), INIT{}, 程序运行, END{}。 这篇文章最初是由 Nathan Torkington 编写，现在由邮件列表(perl5-porters@perl.org.)维护 LinuxForum.Net CMPP 中文手册页计划 http://cmpp.linuxforum.net * Tue Nov 25 2003 郭锐(sunny65535) <sunny65535@263.net>
- 初始版本