私たちが一般的に使用する高級言語は数多くありますが、より有名なものには、CC、Python、PHP、Go、Pascal などが含まれます。これらの言語は、その実行方法に応じて、コンパイル言語とインタプリタ言語の 2 種類に大別されます。
その中で、コンパイル言語には、CC、Pascal、Goなどが含まれます。ここでいうコンパイルとは、アプリケーションのソースプログラムを実行する前に、プログラムのソースコードをアセンブリ言語に「翻訳」し、さらにソフトウェアやハードウェアの環境に応じてターゲットファイルにコンパイルすることを意味します。一般に、コンパイル作業を完了するツールをコンパイラと呼びます。インタープリタ言語は、プログラムの実行中に機械語に「翻訳」されます。ただし、「翻訳」は1回なので実行効率は低いです。インタプリタの仕事は、ソース コードを解釈された言語で「翻訳」するプログラムです。
コンパイルおよびインタープリタ言語がどのように動作するかについてさらに詳しく説明しましょう。
1. コンパイル言語とインタープリタ言語
C 言語コードは、読み取り可能になる前に、プリコンパイル、コンパイル、アセンブル、およびリンクが必要であることがわかっています。バイナリーファイル。 hello.c を例に挙げます。
#include<stdio.h> int main(){ printf("hello world"); return 1; }
この C コードでは、main はプログラム エントリ関数であり、その機能は文字列「hello world」を画面に出力することです。コンパイルと実行のプロセスを図 1 に示します。
図 1 コンパイル言語実行の概略図
ステップ 1: C 言語コードの前処理 (依存関係の処理、マクロの置換など) 、など)。上記のコード例と同様に、#inlcude
ステップ 2: コンパイルします。コンパイラは C 言語をアセンブリ言語プログラムに変換します。通常、1 つの C 言語は複数行のアセンブリ コードを表します。同時に、コンパイラはプログラムを最適化し、ターゲットのアセンブリプログラムを生成します。
ステップ 3: コンパイルされたアセンブリ言語は、アセンブラを通じてターゲット プログラム hello.o にアセンブルされます。
ステップ 4: リンク。プログラムには、サンプル プログラムの printf() 関数など、共有オブジェクト ファイルが含まれることがよくあります。このファイルは静的ライブラリ内にあり、リンカー (Uinx コネクタ ld など) を介してリンクする必要があります。
C 言語に代表されるコンパイル言語のコード更新は、上記の手順を実行する必要があります。
私たちは、主にコンパイルされるソース コードに基づいて、コンパイル言語とインタプリタ言語を区別します。ターゲット プラットフォーム CPU 命令のタイミング。コンパイル言語の場合、コンパイル結果はすでに現在の CPU システム用の命令になっていますが、インタープリタ言語の場合は、最初に中間コードにコンパイルしてから、インタープリタ言語の特定の仮想マシンを介して特定の CPU システムの命令に変換する必要があります。実行。解釈された言語は、実行時にターゲット プラットフォームの命令に翻訳されます。通訳言語は「遅い」とよく言われますが、遅いのは主にそれが原因です。
PHP7では、ソースコードを字句解析して複数の文字列単位に分割し、分割された文字列をトークンと呼びます。それぞれの独立したトークンは完全なセマンティクスを表現することはできず、構文分析段階を経てトークンを抽象構文ツリー (AST) に変換する必要があります。その後、抽象構文ツリーは実行用のマシン命令に変換されます。 PHP では、これらの命令はオペコードと呼ばれます (オペコードについては後で詳しく説明します。ここでは、読者はそれを CPU 命令と考えることができます)。
AST を生成するステップまでは、コンパイル言語とインタープリタ言語が通過するプロセスは似ています。違いは、抽象構文ツリーの後に始まります。
図 2 は、PHP (特別な命令が指定されていない場合、この章で言及されている PHP は PHP7 バージョンです) コードが実行される簡略化されたステップを示しています。最後のステップの左側の分岐はコンパイル言語のプロセスです。 。
#図 2 インタープリタ型言語の実行図の例として PHP を取り上げますステップ 1: ソース コードは、字句解析によるトークン;
ステップ 2: 構文アナライザーに基づいて抽象構文ツリー (AST) を生成;
ステップ 3: 抽象構文を変換ツリーをオペコード (オペコード命令セット) に変換すると、PHP がオペコードを解釈して実行します。
次に、基本的な手順に基づいて、PHP 言語の実行原理を改良し、より明確な理解を目指します。
2. PHP7 の実行原理の概要
まず、上記の PHP7 プログラムの実行プロセスを補足します (図 3 を参照)。
図 3 PHP7 言語で書かれたプログラムの実行プロセス図
ステップ 1: 字句解析により PHP コードを意味のあるコードに変換する識別トークン。このステップの字句アナライザーは Re2c を使用して実装されます。
第2步:语法分析将Token和符合文法规则的代码生成抽象语法树。语法分析器基于Bison实现。语法分析使用了巴科斯范式(BNF)来表达文法规则,Bison借助状态机、状态转移表和压栈、出栈等一系列操作,生成抽象语法树。
第3步:上步的抽象语法树生成对应的opcode,被虚拟机执行。opcode是PHP7定义的一组指令标识,指令对应着相应的handler(处理函数)。当虚拟机调用opcode,会找到opcode背后的处理函数,执行真正的处理。以我们常见的echo语句为例,其对应的opcode便是ZEND_ECHO。
注意:这里为了便于理解词法分析和语法分析过程,将两者分开描述。但实际情况,出于效率考虑,两个过程并非完全独立。
下面,我们通过一段示例代码,来建立PHP7运转的初步理解。
示例代码如下:
<?phpecho "hello world";
从图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("<?php echo \"hello world\";"));'
输出结果为:
Array ( [0] => Array ( [0] => 379 [1] => <?php [2] => 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 328
3)源码中的空格,对应的Token叫T_WHITESPACE,值为382:
#define T_WHITESPACE 382
4)字符串“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_HANDLERZEND_RETURN // handler: ZEND_RETURN_SPEC_CONST_HANDLER
可见,ZEND_ECHO对应的handler是ZEND_ECHO_SPEC_CONST_HANDLER。此handler的实现的功能便是预期的“hello world”语句的输出。
相关推荐:《PHP7新特性手册》
以上がPHP7言語の実行原理(PHP7ソースコード解析)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。