ホームページ >バックエンド開発 >PHPチュートリアル >PHPコードの実行プロセスを深く理解する_PHPチュートリアル
言語は人々がコミュニケーションを図るために使用する表現記号であり、それぞれの言語には独自の記号、表現、ルールがあります。 プログラミング言語に関しても、特定の記号、特定の式、規則で構成されています。自然言語であってもプログラミング言語であっても、言語の機能はコミュニケーションです。それらの違いは、自然言語は人間間のコミュニケーションのツールであるのに対し、プログラミング言語は人間と機械の間のコミュニケーションチャネルであるということです。
PHP 言語に関する限り、それは特定のルールに準拠した合意された命令のセットでもあります。 プログラマーがアイデアを PHP 言語で実装した後、これらの PHP 命令は、PHP の仮想マシン (正確には、PHP の言語エンジン Zend のはずです) を通じて C 言語に変換されます。 (下位レベルの命令セットとして理解できます) 命令が作成され、C 言語はアセンブリ言語に変換され、最後にアセンブリ言語はプロセッサの規則に従って実行されるマシンコードに変換されます。これは、より高いレベルの抽象化を継続的に具体化し、洗練させるプロセスです。
ある言語から別の言語への変換はコンパイルと呼ばれ、2 つの言語はそれぞれソース言語およびターゲット言語と呼ばれます。 このコンパイル プロセスは、ターゲット言語がソース言語よりも低レベル (または低レベル) の場合に発生します。
言語変換のコンパイルプロセスは、通常、字句解析、構文解析、意味解析、中間コード生成、コード最適化、ターゲットコード生成などの一連のプロセスに分割されます。 前段階 (字句解析、構文解析、意味解析) の機能は、コンパイラのフロントエンドと呼ぶことができるソース プログラムを解析することです。 次の段階 (中間コード生成、コード最適化、ターゲット コード生成) の機能は、コンパイラーのバックエンドと呼ぶことができるターゲット プログラムを構築することです。 言語をコンパイル言語と呼ぶのは、プログラムを実行する前に翻訳プロセスが存在するためです。重要なのは、全く異なる形式の同等のプログラムが生成されることです。 PHP がインタープリタ型言語と呼ばれる理由は、PHP を生成するそのようなプログラムが存在しないためです。
生成されるのは中間コード Opcode であり、これは PHP の内部データ構造にすぎません。
例えば、簡単なプログラムを書いてみましょう
<?php echo "Hello World!"; $a = 1 + 1; echo $a; ?>この単純なプログラムの実行プロセスは何ですか?実際、実行プロセスは前述したように 4 つのステップに分かれています。 (PHP言語エンジンZendの実行処理のみを指し、Webサーバーの実行処理は含みません。)
れーれー
注 2: APC などの現在のキャッシュでは、PHP がオペコードをキャッシュできるようにすることで、リクエストが届くたびに最初の 3 つの手順を繰り返す必要がなくなり、PHP の実行速度が大幅に向上します。
では、字句解析とは何でしょうか? コンパイルの原理を学習した学生は、字句解析の基本テーブルであるコンパイルの原理を理解しているはずです。
PHP では、当初は Flex が使用されていましたが、その後、MySQL では字句解析に Flex が使用されるようになりました。また、Lex は UNIX システムなどの標準的な字句解析ツールです。 これらのツールはすべて、字句解析ルールを表す入力文字列ストリームを読み取り、C 言語で実装された字句解析ソース コードを出力します。 ここでは、PHP の現在の字句解析ツールである re2c のみを紹介します。 ソース コード ディレクトリ内の Zend/zend_lang_scanner.l ファイルは re2c ルール ファイルです。ルール ファイルを変更する必要がある場合は、re2c をインストールして再コンパイルし、新しいルール ファイルを生成する必要があります。 Zend/zend_ language_scanner.c は、Zend/zend_ language_scanner.l に基づいて入力されます。 PHP コードは字句解析を実行して「単語」を 1 つずつ取得します。
PHP 4.2 以降、token_get_all という関数が提供されています。この関数は、PHP コードをスキャンしてトークンに変換します。
次のコードを使用して、token_get_all 関数を使用して、最初に説明した PHP コードを処理します。
リーリー
注: 理解しやすく表示するために、token_name 関数を使用してパーサー コードをシンボル名の説明に変更しました。元のバージョンを見たい子供たちがいる場合は、上記のコードの 10 行目と 11 行目のコメントを削除できます。
インタープリターのコードネームの詳細なリストについては、http://www.php.net/manual/zh/tokens.php
を参照してください。
得られた結果は次のとおりです:
れーれー
各ソースコード内の文字は、対応する順序で表示されます。
ラベル、演算子、ステートメントなどのその他の部分は 3 つの部分に変換されます
1. トークン ID インタープリター コード (つまり、T_ECHO、T_STRING など、Zend 内のトークンの対応するコードを変更します)
2. ソースコード内のオリジナルコンテンツ
3. この単語はソースコードの何行目ですか?
2. トークンを解析し、シンプルで意味のある表現に変換します
然后将剩余的Tokens转换成一个一个的简单的表达式
1.echo a constant string 2.add two numbers together 3.store the result of the prior expression to a variable 4.echo a variable
Bison是一种通用目的的分析器生成器。它将LALR(1)上下文无关文法的描述转化成分析该文法的C程序。 使用它可以生成解释器,编译器,协议实现等多种程序。 Bison向上兼容Yacc,所有书写正确的Yacc语法都应该可以不加修改地在Bison下工作。 它不但与Yacc兼容还具有许多Yacc不具备的特性。
Bison分析器文件是定义了名为yyparse并且实现了某个语法的函数的C代码。 这个函数并不是一个可以完成所有的语法分析任务的C程序。 除此这外我们还必须提供额外的一些函数: 如词法分析器、分析器报告错误时调用的错误报告函数等等。 我们知道一个完整的C程序必须以名为main的函数开头,如果我们要生成一个可执行文件,并且要运行语法解析器, 那么我们就需要有main函数,并且在某个地方直接或间接调用yyparse,否则语法分析器永远都不会运行。
在PHP源码中,词法分析器的最终是调用re2c规则定义的lex_scan函数,而提供给Bison的函数则为zendlex。 而yyparse被zendparse代替。
在PHP实现内部,opcode由如下的结构体表如下:
struct _zend_op { opcode_handler_t handler; // 执行该opcode时调用的处理函数 znode result; znode op1; znode op2; ulong extended_value; uint lineno; zend_uchar opcode; // opcode代码 };
和CPU的指令类似,有一个标示指令的opcode字段,以及这个opcode所操作的操作数。
PHP不像汇编那么底层, 在脚本实际执行的时候可能还需要其他更多的信息,extended_value字段就保存了这类信息。
其中的result域则是保存该指令执行完成后的结果。
PHP脚本编译为opcode保存在op_array中,其内部存储的结构如下:
struct _zend_op_array { /* Common elements */ zend_uchar type; char *function_name; // 如果是用户定义的函数则,这里将保存函数的名字 zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; zend_bool pass_rest_by_reference; unsigned char return_reference; /* END of common elements */ zend_bool done_pass_two; zend_uint *refcount; zend_op *opcodes; // opcode数组 zend_uint last,size; zend_compiled_variable *vars; int last_var,size_var; // ... }
ZEND_API void execute(zend_op_array *op_array TSRMLS_DC) { // ... 循环执行op_array中的opcode或者执行其他op_array中的opcode }
前面提到每条opcode都有一个opcode_handler_t的函数指针字段,用于执行该opcode。
PHP有三种方式来进行opcode的处理:CALL,SWITCH和GOTO。
PHP默认使用CALL的方式,也就是函数调用的方式, 由于opcode执行是每个PHP程序频繁需要进行的操作,
可以使用SWITCH或者GOTO的方式来分发, 通常GOTO的效率相对会高一些,
不过效率是否提高依赖于不同的CPU。
在我们上面的例子中,我们的PHP代码会被Parsing成:* ZEND_ECHO 'Hello World%21' * ZEND_ADD ~0 1 1 * ZEND_ASSIGN !0 ~0 * ZEND_ECHO !0 * ZEND_RETURN 1你可能会问了,我们的$a去那里了?这个要介绍操作数了,每个操作数都是由以下俩个部分组成:
a)op_type : 为IS_CONST, IS_TMP_VAR, IS_VAR, IS_UNUSED, or IS_CV b)u,一个联合体,根据op_type的不同,分别用不同的类型保存了这个操作数的值(const)或者左值(var)