DOKK / manpages / debian 11 / manpages-zh / perlcompile.7.zh_TW
PERLCOMPILE(7) Perl Programmers Reference Guide PERLCOMPILE(7)

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_printmy() (詞法) 的一個變量,在第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.)維護

譯者

郭銳(sunny65535) <sunny65535@263.net>

本頁面中文版由中文 man 手冊頁計劃提供。
中文 man 手冊頁計劃:https://github.com/man-pages-zh/manpages-zh

2003-11-25 perl v5.8.3