加入收藏 | 设为首页 | 会员中心 | 我要投稿 均轻资讯网 (https://www.ijunqing.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > Asp教程 > 正文

ASP 变量 PHP Zval结构

发布时间:2022-10-08 15:43:32 所属栏目:Asp教程 来源:
导读:  变量的内部实现

  变量是一个语言实现的基础,变量有两个组成部分:变量名、变量值,PHP中可以将其对应为:zval、zend_value,这两个概念一定要区分开,PHP中变量的内存是通过引用计数进行管理的,而且PHP
  变量的内部实现
 
  变量是一个语言实现的基础,变量有两个组成部分:变量名、变量值,PHP中可以将其对应为:zval、zend_value,这两个概念一定要区分开,PHP中变量的内存是通过引用计数进行管理的,而且PHP7中引用计数是在zend_value而不是zval上,变量之间的传递、赋值通常也是针对zend_value。
 
  PHP中可以通过$关键词定义一个变量:$a;,在定义的同时可以进行初始化:$a = "hi~";,注意这实际是两步:定义、初始化,只定义一个变量也是可以的,可以不给它赋值,比如:
 
  $a;
  $b = 1;
 
  这段代码在执行时会分配两个zval。
 
  接下来我们具体看下变量的结构以及不同类型的实现。
 
  变量的基础结构
 
  //zend_types.h
  typedef struct _zval_struct     zval;
 
  typedef union _zend_value {
      zend_long         lval;    //int整形
      double            dval;    //浮点型
      zend_refcounted  *counted;
      zend_string      *str;     //string字符串
      zend_array       *arr;     //array数组
      zend_object      *obj;     //object对象
      zend_resource    *res;     //resource资源类型
      zend_reference   *ref;     //引用类型,通过&$var_name定义的
      zend_ast_ref     *ast;     //下面几个都是内核使用的value
      zval             *zv;
      void             *ptr;
      zend_class_entry *ce;
      zend_function    *func;
      struct {
          uint32_t w1;
          uint32_t w2;
      } ww;
  } zend_value;
 
  struct _zval_struct {
      zend_value        value; //变量实际的value
      union {
          struct {
              ZEND_ENDIAN_LOHI_4( //这个是为了兼容大小字节序,小字节序就是下面的顺序,大字节序则下面4个顺序翻转
                  zend_uchar    type,         //变量类型
                  zend_uchar    type_flags,  //类型掩码,不同的类型会有不同的几种属性,内存管理会用到
                  zend_uchar    const_flags,
                  zend_uchar    reserved)     //call info,zend执行流程会用到
          } v;
          uint32_t type_info; //上面4个值的组合值,可以直接根据type_info取到4个对应位置的值
      } u1;
      union {
          uint32_t     var_flags;
          uint32_t     next;                 //哈希表中解决哈希冲突时用到
          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结构比较简单,内嵌一个union类型的zend_value保存具体变量类型的值或指针,zval中还有两个union:u1、u2:
 
  从zend_value可以看出,除long、double类型直接存储值外,其它类型都为指针,指向各自的结构。
 
  类型
 
  zval.u1.type类型:
 
  /* regular data types */
  #define IS_UNDEF                    0
  #define IS_NULL                     1
  #define IS_FALSE                    2
  #define IS_TRUE                     3
  #define IS_LONG                     4
  #define IS_DOUBLE                   5
  #define IS_STRING                   6
  #define IS_ARRAY                    7
  #define IS_OBJECT                   8
  #define IS_RESOURCE                 9
  #define IS_REFERENCE                10
 
  /* constant expressions */
  #define IS_CONSTANT                 11
  #define IS_CONSTANT_AST             12
 
  /* fake types */
  #define _IS_BOOL                    13
  #define IS_CALLABLE                 14
 
  /* internal types */
  #define IS_INDIRECT                 15
  #define IS_PTR                      17
 
  标量类型
 
  最简单的类型是true、false、long、double、null,其中true、false、null没有value,直接根据type区分,而long、double的值则直接存在value中:zend_long、double,也就是标量类型不需要额外的value指针。
 
  字符串
 
  PHP中字符串通过zend_string表示:
 
  struct _zend_string {
      zend_refcounted_h gc;
      zend_ulong        h;                /* hash value */
      size_t            len;
      char              val[1];
  };
 
  事实上字符串又可具体分为几类:IS_STR_PERSISTENT(通过malloc分配的)、IS_STR_INTERNED(php代码里写的一些字面量,比如函数名、变量值)、IS_STR_PERMANENT(永久值,生命周期大于request)、IS_STR_CONSTANT(常量)、IS_STR_CONSTANT_UNQUALIFIED,这个信息通过flag保存:zval.value->gc.u.flags,后面用到的时候再具体分析。
 
  数组
 
  array是PHP中非常强大的一个数据结构,它的底层实现就是普通的有序HashTable,这里简单看下它的结构,下一节会单独分析数组的实现。
 
  typedef struct _zend_array HashTable;
 
  struct _zend_array {
      zend_refcounted_h gc; //引用计数信息,与字符串相同
      union {
          struct {
              ZEND_ENDIAN_LOHI_4(
                  zend_uchar    flags,
                  zend_uchar    nApplyCount,
                  zend_uchar    nIteratorsCount,
                  zend_uchar    reserve)
          } v;
          uint32_t flags;
      } u;
      uint32_t          nTableMask; //计算bucket索引时的掩码
      Bucket           *arData; //bucket数组
      uint32_t          nNumUsed; //已用bucket数
      uint32_t          nNumOfElements; //已有元素数,nNumOfElements <= nNumUsed,因为删除的并不是直接从arData中移除
      uint32_t          nTableSize; //数组的大小,为2^n
      uint32_t          nInternalPointer; //数值索引
      zend_long         nNextFreeElement;
      dtor_func_t       pDestructor;
  };
 
  对象/资源
 
  struct _zend_object {
      zend_refcounted_h gc;
      uint32_t          handle;
      zend_class_entry *ce; //对象对应的class类
      const zend_object_handlers *handlers;
      HashTable        *properties; //对象属性哈希表
      zval              properties_table[1];
  };
 
  struct _zend_resource {
      zend_refcounted_h gc;
      int               handle;
      int               type;
      void             *ptr;
  };
 
  对象比较常见,资源指的是tcp连接、文件句柄等等类型,这种类型比较灵活,可以随意定义struct,通过ptr指向,后面会单独分析这种类型,这里不再多说。
 
  引用
 
  引用是PHP中比较特殊的一种类型,它实际是指向另外一个PHP变量,对它的修改会直接改动实际指向的zval,可以简单的理解为C中的指针,在PHP中通过&操作符产生一个引用变量,也就是说不管以前的类型是什么,&首先会创建一个zend_reference结构,其内嵌了一个zval,这个zval的value指向原来zval的value(如果是布尔、整形、浮点则直接复制原来的值),然后将原zval的类型修改为IS_REFERENCE,原zval的value指向新创建的zend_reference结构。
 
  struct _zend_reference {
      zend_refcounted_h gc;
      zval              val;
  };
 
  结构非常简单,除了公共部分zend_refcounted_h外只有一个val,举个示例看下具体的结构关系:
 
  $a = "time:" . time();      //$a    -> zend_string_1(refcount=1)
  $b = &$a;                   //$a,$b -> zend_reference_1(refcount=2) ->
  zend_string_1(refcount=1)
 
  注意:引用只能通过&产生,无法通过赋值传递,比如:
 
  $a = "time:" . time();      //$a    -> zend_string_1(refcount=1)
  $b = &$a;                   //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)
  $c = $b;                    //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=2)
                              //$c    ->                                 ---
 
  $b = &$a这时候$a、$b的类型是引用,但是$c = $b并不会直接将$b赋值给$c,而是把$b实际指向的zval赋值给$c,如果想要$c也是一个引用则需要这么操作:
 
  $a = "time:" . time();      //$a       -> zend_string_1(refcount=1)
  $b = &$a;                   //$a,$b    -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)
  $c = &$b;/*或$c = &$a*/     //$a,$b,$c -> zend_reference_1(refcount=3) -> zend_string_1(refcount=1)
 
  这个也表示PHP中的引用只可能有一层,不会出现一个引用指向另外一个引用的情况,也就是没有C语言中指针的指针的概念。
 
  内存管理
 
  接下来分析下变量的分配、销毁。
 
  在分析变量内存管理之前我们先自己想一下可能的实现方案,最简单的处理方式:定义变量时alloc一个zval及对应的value结构(ref/arr/str/res...),赋值、函数传参时硬拷贝一个副本,这样各变量最终的值完全都是独立的,不会出现多个变量同时共用一个value的情况,在执行完以后直接将各变量及value结构free掉。
 
  这种方式是可行的,而且内存管理也很简单,但是,硬拷贝带来的一个问题是效率低,比如我们定义了一个变量然后赋值给另外一个变量,可能后面都只是只读操作,假如硬拷贝的话就会有多余的一份数据,这个问题的解决方案是:引用计数+写时复制。PHP变量的管理正是基于这两点实现的。
 
  引用计数
 
  引用计数是指在value中增加一个字段refcount记录指向当前value的数量,变量复制、函数传参时并不直接硬拷贝一份value数据,而是将refcount++ASP 变量,变量销毁时将refcount--,等到refcount减为0时表示已经没有变量引用这个value,将它销毁即可。
 
  $a = "time:" . time();   //$a       ->  zend_string_1(refcount=1)
  $b = $a;                 //$a,$b    ->  zend_string_1(refcount=2)
  $c = $b;                 //$a,$b,$c ->  zend_string_1(refcount=3)
 
  unset($b);               //$b = IS_UNDEF  $a,$c ->  zend_string_1(refcount=2)
 
  引用计数的信息位于给具体value结构的gc中:
 
  typedef struct _zend_refcounted_h {
      uint32_t         refcount;          /* reference counter 32-bit */
      union {
          struct {
              ZEND_ENDIAN_LOHI_3(
                  zend_uchar    type,
                  zend_uchar    flags,    /* used for strings & objects */
                  uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
          } v;
          uint32_t type_info;
      } u;
  } zend_refcounted_h;
 
  从上面的zendvalue结构可以看出并不是所有的数据类型都会用到引用计数,long、double直接都是硬拷贝,只有value是指针的那几种类型才_可能**会用到引用计数。
 

(编辑:均轻资讯网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章