·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> 网站建设开发 >> php网站开发 >> 深入理解PHP内核 第三章 第一节 变量的结构

深入理解PHP内核 第三章 第一节 变量的结构

作者:佚名      php网站开发编辑:admin      更新时间:2022-07-23

第一节 变量的结构

每门计算机语言都需要一些容器来保存变量数据。在一些语言当中,变量都有特定的类型,如字符串,数组,对象等等。比如C和Pascal就属于这种。而php则没有这样的类型。在PHP中,一个变量在某一行是字符串,可能到下一行就变成了数字。变量可以经常在不同的类型间轻易的转化,甚至是自动的转换。 PHP之所以成为一个简单并且强大的语言,很大一部分的原因是它拥有弱类型的变量。但是有些时候这也会带来一些问题。在PHP内部,所有的变量都保存在zval结构中,也就是说,zval使用同一种结构存储了包括int、array、string等不同数据类型。它不仅仅包含变量的值,也包含变量的类型。变量容器中包含一些Zend引擎用来区分是否引用的字段。同时它也包含这个值的引用计数。

那么,zval是如何做到的呢,下面我们一起来揭开面纱。

一.PHP变量类型在内核中的存储结构

PHP是一种弱类型的语言,这就意味着在声明或使用变量的时候,并不需要显式指明其数据类型。但是,PHP是由C来实现的,大家都知道C对变量的类型管理是非常严格的,强类型的C是这样实现弱类型的PHP变量类型的:

1.在PHP中,存在8种变量类型,可以分为三类

  • 标量类型: boolean integer float(double) string
  • 复合类型: array object
  • 特殊类型: resource NULL

在变量声明的开始,ZE判断用户变量的类型,并存入到以下zval结构体中。zval结构体定义在Zend/zend.h文件,其代码如下:

typedef struct _zval_struct zval;
...
struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    zend_uint refcount__gc;
    zend_uchar type;    /* active type */
    zend_uchar is_ref__gc;
};

2.初始化变量类型:

在上面的结构体中有四个值,其含义为:

属性名 含义 默认值
refcount__gc 表示引用计数 1
is_ref__gc 表示是否为引用 0
value 存储着变量的值信息
type 记录变量的内部类型

在PHP5.3之后,由于引入了垃圾收集机制,引用计数和是否为引用的属性名为refount__gc和is_ref_gc。在此之前为refcount和is__ref。

在变量的初始化过程中,ZE会将变量的类型(type)值根据其变量类型置为:IS_NULL, IS_BOOL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_RESOURCE 之一。

PHP的实现中,如何判断变量是属于哪种类型的呢?(下节介绍)

二.变量的值在_zval_value中的存储

在上面大家不难发现,所有的php变量都是存储于zval结构中,其中变量值存储在zvalue_value联合体中:

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;

各种类型的数据会使用不同的方法来进行变量值的存储,其对应变量的赋值方式:

  • 一般类型
变量类型
boolean ZVAL_BOOL 布尔型/整型的变量值存储于(zval).value.lval中,其类型也会以相应的IS_*进行存储。
 Z_TYPE_P(z)=IS_BOOL/LONG;  Z_LVAL_P(z)=((b)!=0); 
integer ZVAL_LONG
float ZVAL_DOUBLE
null ZVAL_NULL NULL值的变量值不需要存储,只需要把(zval).type标为IS_NULL。
 Z_TYPE_P(z)=IS_NULL; 
resource ZVAL_RESOURCE 资源类型的存储与其他一般变量无异,但其初始化及存取实现则不同。
 Z_TYPE_P(z) = IS_RESOURCE;  Z_LVAL_P(z) = l; 
  • 字符串Sting

字符串类型的存储有别于上述一般类型,因为C中的字符串变量实际上是指向一个字符数组的头指针。所以,PHP在实现字符串变量时,也采用指针的方式,在_zvalue_value数据结构中,存在下面的结构体内,其中*val就存储了指向字符串的指针,而len则存储了字符的长度。

struct {
    char *val;
    int len;
} str;

从这里可以看出strlen()函数是不会重新计算字符串长度的,只是返回str结构体中的len的值。

  • 数组Array

数组是PHP中最常用,也是最强大变量类型,它可以存储其他类型的数据,而且提供各种内置操作函数。数组的存储相对于其他变量要复杂一些,需要使用其它两种数据结构HashTable和Bucket。

typedef struct _hashtable { 
    uint nTableSize;        // hash Bucket的大小,最小为8,以2x增长。
    uint nTableMask;        // nTableSize-1 , 索引取值的优化
    uint nNumOfElements;    // hash Bucket中当前存在的元素个数, count()函数会直接返回此值 
    ulong nNextFreeElement; // 标记hash Bucket当前索引数
    Bucket *pInternalPointer;   // 当前遍历的指针(foreach比for快的原因之一)
    Bucket *pListHead;          // 存储数组头元素指针
    Bucket *pListTail;          // 存储数组尾元素指针
    Bucket **arBuckets;         // 存储hash数组
    dtor_func_t pDestructor;
    zend_bool persistent;
    unsigned char nApplyCount; // 标记当前hash Bucket被递归访问的次数(防止多次递归)
    zend_bool bApplyPRotection;// 标记当前hash桶允许不允许多次访问,不允许时,最多只能递归3次
#if ZEND_DEBUG
    int inconsistent;
#endif
} HashTable;
 
....
 
typedef struct bucket {
    ulong h;            //对char *key进行hash后的值,或者是用户指定的数字索引值
    uint nKeyLength;    //hash关键字的长度,如果数组索引为数字,此值为0
    void *pData;        //指向value,一般是用户数据的副本,如果是指针数据,则指向pDataPtr
    void *pDataPtr;     //如果是指针数据,此值会指向真正的value,同时上面pData会指向此值
    struct bucket *pListNext;   //整个hash表的下一元素
    struct bucket *pListLast;
    struct bucket *pNext;       //存放在同一个hash Bucket内的下一个元素
    struct bucket *pLast;
    char arKey[1];  
    /*存储字符索引,此项必须放在最未尾,因为此处只字义了1个字节,存储的实际上是指向char *key的值,
    这就意味着可以省去再赋值一次的消耗,而且,有时此值并不需要,所以同时还节省了空间。
    */
} Bucket;

从代码中不难发现,数组的存储是由_zval_struct , _zvalue_value,HashTable,Bucket 共同完成的。上面的注释中标出了结构中的主要属性的作用。

  • 对象Object

对象是一种复合型的数据,其需要存储较多元化的数据,如属性,方法,以及自身的一些性质。对象在PHP中是使用一种zend_object_value的结构体来存放。其代码如下:

typedef struct _zend_object_value {
    zend_object_handle handle;  //  unsigned int类型,是
    zend_object_handlers *handlers;
} zend_object_value;

handle字段是 EG(objects_store).object_buckets的索引,用来存取对应对象的相关数据。zend_object_handlers是一个包含许多方法指针的结构体。关于这个结构体及对象相关的类的结构_zend_class_entry,将在第五章节作详细介绍。