PHP变量在内核中的实现

我们都知道PHP是一个弱类型语言,它的变量理论上可以存储任何类型的数据。

那么,PHP的变量在内核中究竟是怎么实现的呢?

在PHP内核中,变量称为zval,变量的值称为zend_value,注意这是两个不同的东西。

PHP中变量的内存是通过引用计数的方式进行管理的,在PHP7之前,zval容器中有两个字节的额外信息,一个是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来。另一个是"refcount",用以标识指向这个zval变量容器的变量个数。

zend.h中的定义如下(PHP7之前的版本):

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

而PHP7发布之后把这引用计数信息放到了zend_value中,其变量之间的传递和赋值一般也是针对zend_value。

我们下面的讨论如无特殊说明,都是基于PHP7。

PHP7之后zval变化较大,我们先来看变量基础结构的定义

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; //一些辅助值
};


和PHP7之前版本一样,zval直接内嵌一个zend_value用以保存具体变量类型对应的值或者指针,但是注意zend_value的定义PHP7和之前版本是不一样的。

我们可以看到zval中还有两个union u1和u2

u1中u1.v.type可以用来区分变量的类型,u1.v.type_flags是类型掩码,在变量的内存管理、gc机制中会用到。

u2是个辅助值,假如zval只有value、u1两个值,整个zval的大小也会对齐到16byte,既然不管有没有u2大小都是16byte,把多余的4byte拿出来用于一些特殊用途还是很划算的,比如next在哈希表解决哈希冲突时会用到,还有fe_pos在foreach会用到。

注意因为C 语言联合体的特征是一次只有一个成员是有效的并且分配的内存与需要内存最多的成员匹配(也要考虑内存对齐)。所以在zend_value中,只有zend_long和double是直接存储值的,而其他类型都是指向具体数据结构的指针。这可以避免在声明变量而未赋值时,造成过多的内存消耗

zval.u1.v.type对应的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指针。


参考资料:

https://github.com/pangudashu/php7-internal/blob/master/2/zval.md

http://php.net/manual/zh/features.gc.refcounting-basics.php

  


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值