ホームページ > バックエンド開発 > PHPチュートリアル > [翻訳] PHP における変数の実装 (PHP 開発者のための PHP ソースコード - パート 3)

[翻訳] PHP における変数の実装 (PHP 開発者のための PHP ソースコード - パート 3)

WBOY
リリース: 2016-06-13 12:28:45
オリジナル
957 人が閲覧しました

[翻訳] PHP における変数の実装 (PHP 開発者のための PHP ソース コード - パート 3)

記事のソース: http://www.aintnot.com/2016/02/12/phps-source- code-for-php-developers-part3-variables-ch

原文: http://blog.ircmaxell.com/2012/03/phps-source-code-for-php-developers_21.html

「PHP 開発者のための PHP ソース コード」シリーズの 3 回目の記事では、PHP が内部でどのように動作するかを理解するのに役立つように、前の記事を拡張する予定です。最初の記事では、PHP ソース コードの表示方法、コード構造がどのようなものであるか、および PHP 開発者向けに紹介されたいくつかの基本的な C ポインターを紹介しました。 2回目の記事では機能を紹介します。今回は、PHP の最も便利な構造の 1 つである変数について詳しく見ていきます。

を ZVAL

に変換 PHP のコア コードでは、変数は ZVAL と呼ばれます。この構造が重要であるのには理由があります。それは、PHP が弱い型付けを使用し、C が強い型付けを使用するというだけではありません。では、ZVAL はこの問題をどのように解決するのでしょうか?この質問に答えるには、ZVAL 型の定義を注意深く調べる必要があります。この定義を確認するには、lxr ページの定義検索ボックスで zval を検索してみましょう。一見すると、何も役に立たないように見えます。しかし、zend.h ファイルには行 typedef があります (typedef は C で新しいデータ型を定義する方法です)。これが私たちが探しているものかもしれません。探し続けましょう。当初、これは無関係に見えました。ここには役に立つものは何もありません。ただし、何かを確認するために、_zval_struct 行をクリックしてみましょう。

<span style="color: #008080;">1</span> <span style="color: #0000ff;">struct</span><span style="color: #000000;"> _zval_struct {</span><span style="color: #008080;">2</span> <span style="color: #008000;">/*</span><span style="color: #008000;"> Variable information </span><span style="color: #008000;">*/</span><span style="color: #008080;">3</span> zvalue_value value; <span style="color: #008000;">/*</span><span style="color: #008000;"> value </span><span style="color: #008000;">*/</span><span style="color: #008080;">4</span> <span style="color: #000000;">zend_uint refcount__gc;</span><span style="color: #008080;">5</span> zend_uchar type; <span style="color: #008000;">/*</span><span style="color: #008000;"> active type </span><span style="color: #008000;">*/</span><span style="color: #008080;">6</span> <span style="color: #000000;">zend_uchar is_ref__gc;</span><span style="color: #008080;">7</span> };
ログイン後にコピー

次に、PHP の基礎である zval を取得します。単純そうに思えますよね?はい、それは本当ですが、ここには非常に理にかなった魔法のようなものもあります。これは構造または構造であることに注意してください。基本的に、これはパブリック プロパティのみを持つ PHP のクラスと考えることができます。ここには、valuerefcount__gctypeis_ref__gc の 4 つの属性があります。これらのプロパティを 1 つずつ見てみましょう (順序は省略)。

Value

最初に説明する要素は value 変数で、その型は zvalue_value です。あなたのことは知りませんが、私も聞いたことがありません zvalue_value。それでは、それが何であるかを理解してみましょう。サイトの他の部分と同様に、タイプをクリックしてその定義を確認できます。クリックすると、その定義が次と同じであることがわかります:

<span style="color: #000000;">typedef union _zvalue_value {    </span><span style="color: #0000ff;">long</span> lval; <span style="color: #008000;">/*</span><span style="color: #008000;"> long value </span><span style="color: #008000;">*/</span>    <span style="color: #0000ff;">double</span> dval; <span style="color: #008000;">/*</span><span style="color: #008000;"> double value </span><span style="color: #008000;">*/</span>    <span style="color: #0000ff;">struct</span><span style="color: #000000;"> {        </span><span style="color: #0000ff;">char</span> *<span style="color: #000000;">val;        </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> len;    } str;    HashTable </span>*ht; <span style="color: #008000;">/*</span><span style="color: #008000;"> hash table value </span><span style="color: #008000;">*/</span><span style="color: #000000;">    zend_object_value obj;} zvalue_value;</span>
ログイン後にコピー

さて、ここで少し技術的な話をします。結合の定義がわかりますか?つまり、これは実際には構造体ではなく、別の型であるということです。ただし、そこには複数の種類の変数が含まれています。ここに複数のタイプがある場合、どうやってそれを単一のタイプとみなせるのでしょうか?この質問をしてよかったです。この問題を理解するには、まず最初の記事で説明した C 言語の型を思い出す必要があります。

C では、変数はメモリ アドレスの行のラベルにすぎません。タイプは、どのメモリが使用されるかを識別する単なる方法であるとも言えます。 C では、4 バイト文字列を整数値から分離するために何も使用されません。それらは単なるメモリの 1 ブロックにすぎません。コンパイラはメモリ セグメントを変数として「識別」し、それらの変数を特定の型に変換することによってメモリ セグメントを解決しようとしますが、これは常に成功するとは限りません (ところで、変数が取得したメモリ セグメントを「上書き」すると、セグメンテーション違反が発生します)。

ご存知のとおり、共用体はアクセス方法に応じて異なるように解釈される個別の型です。これにより、複数の型をサポートする値を定義できるようになります。注意すべき点は、すべての種類のデータを保存するために同じメモリを使用する必要があるということです。この例では、64 ビット コンパイラでは、long と double の両方がストレージ用に 64 ビットを占有します。文字列構造は 96 ビットを占めます (文字ポインターの格納に 64 ビット、整数の長さの格納に 32 ビット)。 hash_table は 64 ビットを占有し、zend_object_value は 96 ビットを占有します (32 ビットは要素の格納に使用され、残りの 64 ビットはポインターの格納に使用されます)。共用体全体が最大要素のメモリ サイズを占有するため、ここでは 96 ビットになります。

この共用体をもう一度見てみると、ここには PHP データ型が 5 つしかないことがわかります (long == int、double == float、str == string、hashtable == array、zend_object_value) == オブジェクト)。では、残りのデータ型はどこに行くのでしょうか?この構造は残りのデータ型を格納するには十分であることがわかります。 BOOL は Long (int) を使用して格納しますが、NULL はデータ セグメントを占有せず、RESOURCE も Long を使用して格納します。

TYPE

因为这个value联合体并没有控制它是怎么被访问的,我们需要其他方式来记录变量的类型。这里,我们可以通过数据类型来得出如何访问value的信息。它使用type这个字节来处理这个问题(zend_uchar是一个无符号的字符,或者内存中的一个字节)。它从zend类型常量保留这些信息。这真的是一种魔法,是需要使用zval.type = IS_LONG来定义整型数据。因此这个字段和value字段就足够让我们知道PHP变量的类型和值。

IS_REF

这个字段标识变量是否为引用。那就是说,如果你执行了在变量里执行了$foo = &$bar。如果它是0,那么变量就不是一个引用,如果它是1,那么变量就是一个引用。它并没有做太多的事情。那么,在我们结束_zval_struct之前,再看一看它的第四个成员。

REFCOUNT

这个变量是指向PHP变量容器的指针的计数器。也就是说,如果refcount是1,那就表示有一个PHP变量使用这个容器。如果refcount是2,那就表示有两个PHP变量指向同一个变量容器。单独的refcount变量并没有太多有用的信息,但如果它与is_ref一起使用,就构成了垃圾回收器和写时复制的基础。它允许我们使用同一个zval容器来保存一个或多个PHP变量。refcount的语义解释超出这篇文章的范围,如果你想继续深入,我推荐你查看这篇文档。

这就是ZVAL的所有内容。

它是怎么工作的?

在PHP内部,zval使用跟其他C变量一样,作为内存段或者一个指向内存段的指针(或者指向指针的指针,等等),传递到函数。一旦我们有了变量,我们就想访问它里面的数据。那我们要怎么做到呢?我们使用定义在zend_operators.h文件里面的宏来跟zval一起使用,使得访问数据更简单。有一点很重要的是,每一个宏都有多个拷贝。不同的是它们的前缀。例如,要得出zval的类型,有Z_TYPE(zval)宏,这个宏返回一个整型数据来表示zval参数。但这里还有一个Z_TYPE(zval_p)宏,它跟Z_TYPE(zval)做的事情是一样的,但它返回的是指向zval的指针。事实上,除了参数的属性不一样之外,这两个函数是一样的,实际上,我们可以使用Z_TYPE(*zval_p),但_P和_PP让事情更简单。

我们可以使用VAL这一类宏来获取zval的值。可以调用Z_LVAL(zval)来得到整型值(比如整型数据和资源数据)。调用Z_DVAL(zval)来得到浮点值。还有很多其他的,到这里到此为止。要注意的关键是,为了在C里面获取zval的值,你需要使用宏(或应该)。因此,当我们看见有函数使用它们时,我们就知道它是从zval里面提取它的值。

那么,类型呢?

到现在为止,我们知识谈论了类型和zval的值。我们都知道,PHP帮我们做了类型判断。因此,如果我们喜欢,我们可以将一个字符串当作一个整型值。我们把这一步叫做convert_to_type。要转换一个zval为string值,就调用convert_to_string函数。它会改变我们传递给函数的ZVAL的类型。因此,如果你看到有函数在调用这些函数,你就知道它是在转换参数的数据类型。

Zend_Parse_Paramenters

上一篇文章中,介绍了zend_parse_paramenters这个函数。既然我们知道PHP变量在C里面是怎么表示的,那我们就来深入看看。

ZEND_API <span style="color: #0000ff;">int</span> zend_parse_parameters(<span style="color: #0000ff;">int</span> num_args TSRMLS_DC, <span style="color: #0000ff;">const</span> <span style="color: #0000ff;">char</span> *<span style="color: #000000;">type_spec, ...){    va_list va;    </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> retval;    RETURN_IF_ZERO_ARGS(num_args, type_spec, </span><span style="color: #800080;">0</span><span style="color: #000000;">);    va_start(va, type_spec);    retval </span>= zend_parse_va_args(num_args, type_spec, &va, <span style="color: #800080;">0</span><span style="color: #000000;"> TSRMLS_CC);    va_end(va);    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> retval;}</span>
ログイン後にコピー

现在,从表面上看,这看起来很迷惑。重点要理解的是,va_list类型只是一个使用'...'的可变参数列表。因此,它跟PHP中的func_get_args()函数的构造差不多。有了这个东西,我们可以看到zend_parse_parameters函数马上调用zend_parse_va_args函数。我们继续往下看看这个函数...

这个函数看起来很有趣。第一眼看去,它好像做了很多事情。但仔细看看。首先,我们可以看到一个for循环。这个for循环主要遍历从zend_parse_parameters传递过来的type_spec字符串。在循环里面我们可以看到它只是计算期望接收到的参数数量。它是如何做到这些的研究就留给读者。

继续往下看,我么可以看到有一些合理的检查(检查参数是否都正确地传递),还有错误检查,检查是否传递了足够数量的参数。接下来进入一个我们感兴趣的循环。这个循环真正解析那些参数。在循环里面,我们可以看到有三个if语句。第一个处理可选参数的标识符。第二个处理var-args(参数的数量)。第三个if语句正是我们感兴趣的。可以看到,这里调用了zend_parse_arg()函数。让我们再深入看看这个函数...

继续往下看,我们可以看到这里有一些非常有趣的事情。这个函数再调用另一个函数(zend_parse_arg_impl),然后得到一些错误信息。这在PHP里面是一种很常见的模式,将函数的错误处理工作提取到父函数。这样代码实现和错误处理就分开了,而且可以最大化地重用。你可以继续深入研究那个函数,非常容易理解。但我们现在仔细看看zend_parse_arg_impl()...

现在,我们真正到了PHP内部函数解析参数的步骤。让我们看看第一个switch语句的分支,这个分支用来解析整型参数。接下来的应该很容易理解。那么,我们从分支的第一行开始吧:

<span style="color: #0000ff;">long</span> *p = va_arg(*va, <span style="color: #0000ff;">long</span> *);
ログイン後にコピー

如果你记得我们之前说的,va_args是C语言处理变量参数的方式。所以这里是定义一个整型指针(long在C里面是整型)。总之,它从va_arg函数里面得到指针。这说明,它得到传递给zend_parse_parameters函数的参数的指针。所以这就是我们会用分支结束后的值赋值的指针结果。接下来,我们可以看到进入一个根据传递进来的变量(zval)类型的分支。我们先看看IS_STRING分支(这一步会在传递整型值到字符串变量时执行)。

<span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_STRING:{    </span><span style="color: #0000ff;">double</span><span style="color: #000000;"> d;    </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> type;    </span><span style="color: #0000ff;">if</span> ((type = is_numeric_string(Z_STRVAL_PP(arg), Z_STRLEN_PP(arg), p, &d, -<span style="color: #800080;">1</span>)) == <span style="color: #800080;">0</span><span style="color: #000000;">) {        </span><span style="color: #0000ff;">return</span> <span style="color: #800000;">"</span><span style="color: #800000;">long</span><span style="color: #800000;">"</span><span style="color: #000000;">;    } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (type ==<span style="color: #000000;"> IS_DOUBLE) {        </span><span style="color: #0000ff;">if</span> (c == <span style="color: #800000;">'</span><span style="color: #800000;">L</span><span style="color: #800000;">'</span><span style="color: #000000;">) {            </span><span style="color: #0000ff;">if</span> (d ><span style="color: #000000;"> LONG_MAX) {                </span>*p =<span style="color: #000000;"> LONG_MAX;                </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;            } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (d <<span style="color: #000000;"> LONG_MIN) {                </span>*p =<span style="color: #000000;"> LONG_MIN;                </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;            }        }        </span>*p =<span style="color: #000000;"> zend_dval_to_lval(d);    }}</span><span style="color: #0000ff;">break</span>;
ログイン後にコピー

现在,这个做的事情并没有看起来的那么多。所有的事情都归结与is_numeric_string函数。总的来说,该函数检查字符串是否只包含整数字符,如果不是的话就返回0。如果是的话,它将该字符串解析到变量里(整型或浮点型,p或d),然后返回数据类型。所以我们可以看到,如果字符串不是纯数字,他返回“long”字符串。这个字符串用来包装错误处理函数。否则,如果字符串表示double(浮点型),它先检查这个浮点数作为整型数来存储的话是否太大,然后它使用zend_dval_to_lval函数来帮助解析浮点数到整型数。这就是我们所知道的。我们已经解析了我们的字符串参数。现在继续看看其他分支:

<span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_DOUBLE:    </span><span style="color: #0000ff;">if</span> (c == <span style="color: #800000;">'</span><span style="color: #800000;">L</span><span style="color: #800000;">'</span><span style="color: #000000;">) {        </span><span style="color: #0000ff;">if</span> (Z_DVAL_PP(arg) ><span style="color: #000000;"> LONG_MAX) {            </span>*p =<span style="color: #000000;"> LONG_MAX;            </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;        } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (Z_DVAL_PP(arg) <<span style="color: #000000;"> LONG_MIN) {        </span>*p =<span style="color: #000000;"> LONG_MIN;        </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;    }}</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_NULL:</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_LONG:</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_BOOL:convert_to_long_ex(arg);</span>*p =<span style="color: #000000;"> Z_LVAL_PP(arg);</span><span style="color: #0000ff;">break</span>;
ログイン後にコピー

这里,我们可以看到解析浮点数的操作,这一步跟解析字符串里的浮点数相似(巧合?)。有一个很重要的事情要注意的是,如果参数的标识不是大写'L',它会跟其他类型变量一样的处理方式(这个case语句没有break)。现在,我们还有一个有趣的函数,convert_to_long_ex()。这跟我们之前说到的convert_to_type()函数集合是一类的,该函数转换参数为特定的类型。唯一的不同是,如果参数不是引用的话(因为这个函数在改变数据类型),这个函数就将变量的值及其引用分离(拷贝)了。( The only difference is that it separates (copies) the passed in variable if it's not a reference (since it's changing the type). )这就是写时复制的作用。因此,当我们传递一个浮点数到到一个非引用的整型变量,该函数会把它当作整型来处理,但我们仍然可以得到浮点型数据。

<span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_ARRAY:</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_OBJECT:</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> IS_RESOURCE:</span><span style="color: #0000ff;">default</span><span style="color: #000000;">:</span><span style="color: #0000ff;">return</span> <span style="color: #800000;">"</span><span style="color: #800000;">long</span><span style="color: #800000;">"</span>;
ログイン後にコピー

最后,我们还有另外三个case分支。我们可以看到,如果你传递一个数组、对象、资源或者其他不知道的类型到整型变量中,你会得到错误。

剩下的部分我们留给读者。阅读zend_parse_arg_impl函数对更好地理解额PHP类型判断系统真的很有用。一部分一部分地读,然后尽量追踪在C里面的各种参数的状态和类型。

下一部分

下一部分会在Nikic的博客(我们会在这个系列的文章来回跳转)。在下一篇,他会谈到数组的所有内容。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート