摘要:在中,源代碼首先將進行詞法分析,將源代碼切割為多個字符串單元,分割后的字符串稱之為。圖以為例解釋型語言的執行示意圖第步源碼通過詞法分析得到第步基于語法分析器生成抽象語法樹第步抽象語法樹轉換為指令集合,解釋執行。
順風車運營研發團隊 李志 發表在程序人生 公眾號
我們常用的高級語言有很多種,比較出名的有CC++、Python、 PHP、Go、Pascal等。而這些語言根據運行的方式不同,大體分為兩種:編譯型語言和解釋型語言。
其中,編譯型語言包括CC++、Pascal、Go等。這里說的編譯是指在應用源程序執行之前,就將程序源代碼“翻譯”成匯編語言,然后進一步根據軟硬件環境編譯成目標文件。一般我們稱完成編譯工作的工具叫編譯器。而解釋型語言,在程序運行時才被“翻譯”為機器語言。但是執行一次“翻譯”一次,所以執行效率較低。解釋器的工作就是解釋性語言中,負責“翻譯”源代碼的程序。
下面我們更詳細地討論一下編譯型語言和解釋性語言的運行方式。
一、編譯型語言與解釋型語言
我們知道,對于一段C語言代碼,需要經過預編譯、編譯、匯編和鏈接,才能成為可執行的二進制文件。以hello.c為例:
#includeint main(){ printf("hello world"); return 1; }
對于這段C代碼,main是程序入口函數,實現的功能是打印字符串“hello world” 到屏幕上。編譯和執行過程如圖1所示。
圖1 編譯型語言的執行示意圖
第1步:C語言代碼預處理(比如依賴處理、宏替換等)。如以上代碼示例,#inlcude
第2步:編譯。編譯器會把C語言翻譯成匯編語言程序,一條C語言通常便以為多條匯編代碼。同時編譯器會對程序進行優化,生成目標匯編程序。
第3步:編譯得到的匯編語言通過匯編器再匯編成目標程序hello.o。
第4步:鏈接。程序中往往包含一些共享目標文件,如示例程序中的printf()函數,位于靜態庫,需要經過鏈接器(如Uinx連接器ld)進行鏈接。
以C語言為代表的編譯型語言,代碼發生更新都要經過以上步驟:
我們區別編譯型語言與解釋型語言,主要立足于源代碼被編譯成目標平臺CPU指令的時機。對于編譯型語言,編譯結果已經是針對當前CPU體系的指令;而解釋型語言,需要先編譯成中間代碼,再經由該解釋型語言的特定虛擬機,翻譯成特定CPU體系的指令被執行。解釋型語言是在運行過程中,翻譯為目標平臺的指令。常說解釋型語言“慢”,主要也是慢在這里。
在PHP7中,源代碼首先將進行詞法分析,將源代碼切割為多個字符串單元,分割后的字符串稱之為Token。而一個個獨立的Token無法表達完整語義,需經過語法分析階段,將Token轉換為抽象語法樹(簡稱AST)。之后,抽象語法樹被轉換為機器指令執行。在PHP中,這些指令稱為opcode(后文會對opcode做更詳細的解釋,此處讀者可以看待為CPU指令)。
到AST的生成這一步,編譯型語言與解釋型語言所需經歷的過程相似。從抽象語法樹之后開始產生差異。
圖2是PHP(如無特殊說明,本章提到的PHP均為PHP7版本)代碼被執行的簡化步驟,其中最后一步的左側分支,是編譯型語言的過程。
圖2 以PHP為例解釋型語言的執行示意圖
第1步:源碼通過詞法分析得到Token;
第2步:基于語法分析器生成抽象語法樹(AST);
第3步:抽象語法樹轉換為Opcodes(opcode指令集合),PHP解釋執行Opcodes。
接下來我們在基本步驟的基礎上,細化PHP語言的執行原理,試圖更清晰地建立認知。
二、PHP7的執行原理概述
首先我們補充說明下前文提到的PHP7程序執行過程,請參見圖3。
圖3 PHP7語言編寫的程序的執行過程圖
第1步:詞法分析將PHP代碼轉換為有意義的標識Token。該步驟的詞法分析器使用Re2c實現的。
第2步:語法分析將Token和符合文法規則的代碼生成抽象語法樹。語法分析器基于Bison實現。語法分析使用了巴科斯范式(BNF)來表達文法規則,Bison借助狀態機、狀態轉移表和壓棧、出棧等一系列操作,生成抽象語法樹。
第3步:上步的抽象語法樹生成對應的opcode,被虛擬機執行。opcode是PHP7定義的一組指令標識,指令對應著相應的handler(處理函數)。當虛擬機調用opcode,會找到opcode背后的處理函數,執行真正的處理。以我們常見的echo語句為例,其對應的opcode便是ZEND_ECHO。
注意:這里為了便于理解詞法分析和語法分析過程,將兩者分開描述。但實際情況,出于效率考慮,兩個過程并非完全獨立。
下面,我們通過一段示例代碼,來建立PHP7運轉的初步理解。
示例代碼如下:
從圖3可知,這段代碼首先會被切割為Token。
1. Token
Token是PHP代碼被切割成的有意義的標識。本書介紹的PHP7版本中有137 種Token,在zend_language_parser.h文件中做了定義:
/* Tokens. */ #define END 0 #define T_INCLUDE 258 #define T_INCLUDE_ONCE 259 … #define T_ERROR 392更多Token的含義,感興趣的讀者可以參考《PHP 7底層設計與源碼實現》附錄。
PHP提供了token_get_all()函數來獲取PHP代碼被切割后的Token,可以在深入源碼學習前,粗略查看PHP代碼被切割后的Token。如下代碼片段:
/home/vagrant/php7/bin/php –r "print_r(Token_get_all("輸出結果為:
Array ( [0] => Array ( [0] => 379 [1] => 1 ) [1] => Array ( [0] => 328 [1] => echo [2] => 1 ) [2] => Array ( [0] => 382 [1] => [2] => 1 ) [3] => Array ( [0] => 323 [1] => "hello world" [2] => 1 ) [4] => ; )上文輸出中,二維數組的每個成員數組第一個值為Token對應的枚舉值;第二個值為Token對應的原始字符串內容;第三個值為代碼對應的行號。可以看出,詞法解析器將
1)文本“
#dfine T_OPEN_TAG 379不難理解,它是PHP代碼的起始tag,也就是
2)echo對應的Token是T_ECHO:
#define T_ECHO 3283)源碼中的空格,對應的Token叫T_WHITESPACE,值為382:
#define T_WHITESPACE 3824)字符串“hello world”對應的Token值為323:
#define T_CONSTANT_ENCAPSED_STRING 323可見,Token就是一個個的“詞塊”,但是多帶帶存在的詞塊不能表達完整的語義,還需要借助規則進行組織串聯。語法分析器就是這個組織者。它會檢查語法、匹配Token,對Token進行關聯。
PHP7中,組織串聯的產物就是抽象語法樹(Abstract Syntax Tree,AST)。
2. AST
AST是PHP7版本新特性。在這之前的版本,PHP代碼的執行過程中沒有生成AST這一步。PHP7對抽象語法樹的支持,實現了PHP編譯器和解釋器解耦,有效提升了可維護性。
顧名思義,抽象語法樹具有樹狀結構。AST的節點分為多種類型,對應著不同的PHP語法。在當前章節,我們可以認為節點類型是對語法規則的抽象,例如賦值語句,生成的抽象語法樹節點為ZEND_AST_ASSIGN。而賦值語句的左右操作數,又將作為ZEND_AST_ASSIGN類型節點的孩子。通過這樣的節點關系,構建出抽象語法樹。
如果讀者希望一睹為快,可以直接跳到本書第13章函數的實現,其中圖片描繪了一段簡單的PHP代碼生成的抽象語法樹。
在這里,我們推薦讀者了解下PhpParser工具,可以用它來查看PHP代碼生成的AST。
注意:PHP-Parser是PHP7內核作者之一nikic編寫的將PHP源碼生成AST的工具。源碼見https://github.com/nikic/PHP-...
3. Opcodes
AST扮演了源碼到中間代碼的臨時存儲介質的角色,還需要將其轉換為opcode,才能被引擎直接執行。Opcode只是單條指令,Opcodes是opcode的集合形式,是PHP執行過程中的中間代碼,類似Java中的字節碼。生成之后由虛擬機執行。
我們知道,PHP工程優化措施中有個比較常見的“開啟Opcache”,指的就是這里的Opcodes的緩存(Opcodes Cache)。通過省去從源碼到opcode的階段,引擎可以直接執行緩存的opcode,以此提升性能。
借助vld插件,可以直觀地看到一段PHP代碼生成的opcode:
php -dvld.active=1 hello.php 經過過濾整理,對應的opcode為: line op 1 ECHO 2 RETURN其實在源碼實現中,上述代碼生成的opcode及handler為:
ZEND_ECHO // handler: ZEND_ECHO_SPEC_CONST_HANDLER ZEND_RETURN // handler: ZEND_RETURN_SPEC_CONST_HANDLER可見,ZEND_ECHO對應的handler是ZEND_ECHO_SPEC_CONST_HANDLER。此handler的實現的功能便是預期的“hello world”語句的輸出。
本書的PHP版本中,內核在zend_vm_opcodes.h中定義了186種Opcodes,也可以參考《PHP 7底層設計與源碼實現》附錄部分。
在平時的業務開發中,了解一些 PHP的底層實現,尤其是語法機制的實現,對性能提升、故障排除非常有好處。希望這篇淺文,可以幫助讀者在使用PHP7的同時,了解到編寫的PHP代碼如何被編譯和執行。
(掃上方二維碼7.9折購買)
推薦理由:
滴滴出行專家聯合撰寫,PHP領域大咖夏緒宏、韓天峰、王晶、謝華亮(黑夜路人)、伍星聯袂推薦
全面吃透PHP內核架構、核心實現與內存管理、詞法與句法解析、Zend 虛擬機、函數及關鍵擴展等設計細節與源碼實現
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28985.html
摘要:我們修改上面代碼,再來看下返回值類型限制的情況運行結果這段代碼我們額外聲明了返回值的類型為型。對函數返回值的聲明做了擴充,可以定義其返回值為,無論是否開啟嚴格模式,只要函數中有以外的其他語句都會報錯。 順風車運營研發團隊 王坤 發表至21CTO公眾號(https://mp.weixin.qq.com/s/ph...) showImg(https://segmentfault.c...
摘要:中詞法語法分析,生成抽象語法樹,然后編譯成及被執行均由虛擬機完成。通常情況下這部分是可選部分,主要為便于程序的讀寫方便而使用。指令虛擬機的指令稱為,每條指令對應一個。 作者 陳雷編程語言的虛擬機是一種可以運行中間語言的程序。中間語言是抽象出的指令集,由原生語言編譯而成,作為虛擬機執行階段的輸入。很多語言都實現了自己的虛擬機,比如Java、C#和Lua。PHP語言也有自己的虛擬機,稱為Z...
摘要:重點分析其中宏,返回表的,后面用來作為索引查詢的依據。中資源的解析比中解析簡單快捷很多,得益于其結構的改變。先從逆向通過其在中索引層層關聯,找到該類資源的釋放回調函數,然后對該資源執行釋放回調函數。 PHP 擴展開發的文章,我均已更新至《TIPI》本篇承接上篇 PHP7 使用資源包裹第三方擴展的實現 PHP7 使用資源包裹第三方擴展原理分析 注冊資源類型源碼 [c] ZEND_API ...
閱讀 762·2019-08-29 12:49
閱讀 3550·2019-08-29 11:32
閱讀 3433·2019-08-26 10:43
閱讀 2401·2019-08-23 16:53
閱讀 2047·2019-08-23 15:56
閱讀 1695·2019-08-23 12:03
閱讀 2767·2019-08-23 11:25
閱讀 2084·2019-08-22 15:11