PHPコアのzval

7799 ワード

原文アドレス作者:Tweiホームページ
前言
前の面接で面接官がphpで変数がどのように実現されているのか聞いたことがありますが、残念ながら構造体で実現されているのではないでしょうか.この文章はグーグルがまとめたほうがいいと思ったので、転載して勉強しました.
本文
PHPのデータ型
PHPはC、C++、Javaなどの他のプログラミング言語に比べて弱いタイプの言語であり、変数を使用する場合、そのタイプを宣言する必要がないことを意味します.この特性は私たちに多くの便利さをもたらし、同時にいくつかの罠をもたらすこともあります.では、PHPは本当にデータ型がないという言い方ですか?
もちろん違います.PHPの公式文書では、PHPの変数をスカラータイプ、複雑タイプ、特殊タイプの3つに分類します.スカラータイプには、ブール型(bool)、整数型(int)、浮動小数点型(float)、文字列(string)、複雑なタイプには配列(array)、オブジェクト(object)、特殊なタイプにはNULL、リソース(resource)があります.したがって、PHPの変数が細分化されると、8種類のデータ型があります.
PHPの下層はC言語で実現されていることがよく知られている.我々のPHPスクリプトはZendエンジンを経てCコードに解析して実行します.では、PHPの変数は、C言語でどのように表現されているのでしょうか.最終的にはどのように解析されますか?
答えはzvalです.どんなタイプのPHP変数でも、PHPソースコードにはzvalという構造で統一されています.zvalは、PHP変数のCコード内のコンテナと見なすことができ、この変数の値、タイプなどの関連情報を格納している.
では、zvalの基本構造(C言語の基本的な知識が必要)を見てみましょう.
zvalの基本構造
PHPソースコードにおけるzvalという構造は_zval_structという構造体(struct)であり、具体的にはソースコードのZend/zend.hファイルに定義され、以下は関連コードの抜粋である.
struct _zval_struct {
    zvalue_value value;       /* value */ 
    zend_uint refcount__gc;   /* value of ref count */
    zend_uchar type;          /* active type */ 
    zend_uchar is_ref__gc;    /* if it is a ref variable */ 
}; 
typedef struct _zval_struct zval;

すなわち、PHPのソースコードでは、PHPの様々なタイプの変数をこの構造体で表し、ゴミ回収(GC:Grabage Collection)などの他の機能も実現することができる.
この変数の情報をそれぞれ表す4つのフィールドで構成されていることがわかります.
ZVALUE_VALUE VALUE
valueは変数の実際の値を表すために使用されます.具体的にはzvalue_です.valueの連合体(union):
typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {                    /* string */
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value,used for array */
    zend_object_value obj;      /* object */
} zvalue_value;

が表示されます.zvalue_valueには5つのフィールドしかありませんが、PHPには8種類のデータ型がありますが、5つのフィールドで8種類を表すにはどうすればいいのでしょうか.
これはPHPの設計が比較的巧みな場所であり、フィールドを多重化することでフィールドを減らす目的を達成している.例えば、PHP内部のブール型、整数型およびリソース(リソースの識別子を格納すればよい)はlvalフィールドによって格納される;dvalは浮動小数点型を格納するために使用される;strは文字列を格納する;htは配列を格納する(PHP中の配列は実際にはハッシュテーブルであることに注意する);objはオブジェクトタイプを格納する.すべてのフィールドが0またはNULLに設定されている場合、PHPのNULLが表示され、5つのフィールドに8種類の値が格納されます.
ZEND_UINT REFCOUNT__GC
その接尾辞gcから,このフィールドはごみ回収に関連していることがわかる.
実際には、zvalを指す変数を保存するカウンタです.変数生成時には、1、すなわちrefcount=1に設定します.
変数に対して異なる操作を行うと、その値が変更されます.典型的な付与操作、例えばa=a=bはrefcountに1を加算し、unset()操作はそれに応じて1を減算する.
その値を判断することでゴミ回収が可能です.PHP 5.3より前に、参照カウントのメカニズムを使用してGCを実現します.zvalのrefcountが0に減少すると、Zendエンジンは、zvalを指す変数がないと判断し、zvalが占めるメモリ領域が解放されます.しかし、リファレンスカウントメカニズムを使用するだけでは、ループリファレンスのzvalを解放することはできません.これはメモリリーク(Memory Leak)を引き起こす可能性があります.
5.3以前には、このフィールドの名前はrefcountとも呼ばれていたが、5.3以降、新しいゴミ回収アルゴリズムを導入して循環参照に対処するために、著者らは大量のマクロを加えてrefcountを操作し、エラーをより速く表示させるためにrefcount_と改名した.gcは、refcountを操作するためにマクロを使用するように強制します.
同様に、4番目のフィールドis_もあります.ref,この値はPHPの1つのタイプが参照であるかどうかを示す.PHPのゴミ回収メカニズムを知りたいなら、このブログを参考にしてください.PHPのゴミ回収メカニズム注:変数、シンボル、symbolとも呼ばれます.すべてのシンボルがシンボルテーブル(symbol table)に存在し、異なる役割ドメインが異なるシンボルテーブルを使用していることについて、このブログで説明します.
ZEND_UCHAR TYPE
このフィールドは、変数がPHP 8タイプのどれに属するかを示すために使用されます.zendの内部では、これらのタイプは次のマクロ(コード位置phpsrc/Zend/zend.h)に対応します.
#define IS_NULL     0
#define IS_LONG     1
#define IS_DOUBLE   2
#define IS_BOOL     3
#define IS_ARRAY    4
#define IS_OBJECT   5
#define IS_STRING   6
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_ARRAY   9
#define IS_CALLABLE 10

ZEND_UCHAR IS_REF__GC
このフィールドは、変数が参照変数であるかどうかをマークするために使用されます.通常の変数の場合は0、参照型の変数の場合は1です.この変数はzvalの共有,分離などに影響する.PHPのゴミ回収にも関係しています.
PHP 7のzval
上記のzval構造は、時間の経過とともに、占有空間が大きい(24バイト)、拡張がサポートされていない、オブジェクトや参照効率が悪いなど多くの問題が露呈しているため、PHP 7の際にzvalが大きく変更され、現在ではその構造がこのようになっている.
struct _zval_struct {
    union {
        zend_long         lval;             /* long value */
        double            dval;             /* double value */
        zend_refcounted  *counted;
        zend_string      *str;
        zend_array       *arr;
        zend_object      *obj;
        zend_resource    *res;
        zend_reference   *ref;
        zend_ast_ref     *ast;
        zval             *zv;
        void             *ptr;
        zend_class_entry *ce;
        zend_function    *func;
        struct {
            uint32_t w1;
            uint32_t w2;
        } ww;
    } value;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         /* active type */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t     var_flags;
        uint32_t     next;                 /* hash collision chain */
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
    } u2;
};

大きく見えますが、よく見ると、そのフィールドはすべて連合体で、この新しいzvalは64ビット環境では16バイト(2ポインタsize)しかかかりません.PHP 7のzvalは、元の値を保存しているか、元の値を保存しているポインタを保存しているかのいずれかの値ポインタになりました.
この部分の内容は鳥の兄のGitHubから来ています.
まとめ
  • zvalはC言語で実現されたデータ構造であり、機能はPHP変数の容器である.
  • 変数の様々な情報(タイプや値など)を保存し、ゴミ回収などの他の機能をサポートします.
  • は異なるPHPバージョンで構造が異なる.PHP 7のzvalは16バイト、PHP 5のは24バイトです.

  • リファレンス
    PHPカーネル探索の変数(1)変数の容器-Zval PHPゴミ回収PHP 7のzvalを深く理解する