Scroll to navigation

PERLCOMPILE(7) Perl Programmers Reference Guide PERLCOMPILE(7)

NAME

perlcompile - 關於 Perl 編譯器和翻譯器的介紹

DESCRIPTION 描述

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)。

Using The Back Ends 使用後端

接下來的部分介紹怎樣使用各種各樣的編譯器後端。介紹的順序按照後端的成熟程度排列,所以最為穩定的,經過了驗證的後端會最先介紹,還在試驗中和沒有完成的後端就放到後面描述了。

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.

Module List for the Compiler Suite 編譯套件的模組列表

這個模組是一個自省的(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" 。

KNOWN PROBLEMS 已知的問題

簡單 C 後端目前只儲存以字元和數字命名的型別說明

最佳化的 C 後端會為一些不該為之輸出的模組(比如說 DirHandle)輸出程式碼。而且它不太可能正確地處理正在執行的子程式外部的goto語句(goto &sub is OK)。目前 "goto LABEL" 語句在這個後端中完全不會工作。他還會生成讓C 編譯器頭痛無比的巨大的初始化函式。如果把這個初始化函式分割開是能得到比目前更好的效果的。另外的問題包括:處理無符號的數學問題時不能正確工作;一些操作碼如果按照預設的操作碼機制處理也會有非正常的結果。

BEGIN{} 塊會在編譯你的程式碼的時候被執行。所有的在BEGIN{} 中初始化的外部狀態,如開啟的檔案,初始的資料庫連結等等,會有不正確的表現。為了解決這個問題,Perl中又提供了一個 INIT{} 塊來對應程式編譯之後,正式執行之前要執行的那段程式碼。執行的順序是:BEGIN{}, (後端編譯程式可能這時會儲存狀態), INIT{}, 程式執行, END{}。

AUTHOR 作者

這篇文章最初是由 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