美文网首页
php变量的基本实现

php变量的基本实现

作者: code_nerd | 来源:发表于2018-12-17 23:29 被阅读0次

    PHP作为“世界上最好的语言”,我们都知道php是弱类型语言,即在使用过程中,可以任意改变变量的类型。这对于代码中的灵活性有极大的方便。php底层是由c语言去实现的,那么c语言作为强类型语言,是怎么实现php的这些特性?

    变量的定义

    我们先从变量的定义开始。在百度百科上,变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。变量可以通过变量名访问。意思就是说,变量是来存储值的。那么我们来看看在php中,变量的定义。

    //php
    //定义了变量名为a 变量值为 整数 1
    $a=1;
    //输出$a的值
    echo $a;
    //将变量值 字符串 aaaa 赋给了变量名a
    $a='aaaa';
    

    在php中,变量会在首次为其赋值时被创建,我们在使用中,可以随意改变变量的类型。示例中的代码,简单两行就完成了,变量的定义赋值和重新赋值。而在c语言中,变量则需要先创建才能使用,并且需要严格定义类型。

    在php中变量的值由zval来表示

    php7中,zval的结构定义如下。zval由,value、u1和u2组成。

    struct _zval_struct {
        zend_value        value;            /* value */
        union {
            struct {
                ZEND_ENDIAN_LOHI_4(
                    zend_uchar    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     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 */
            uint32_t     access_flags;         /* class constant access flags */
            uint32_t     property_guard;       /* single property guard */
            uint32_t     extra;                /* not further specified */
        } u2;
    };
    
    typedef union _zend_value {
        zend_long         lval;             //整型
        double            dval;             //浮点型
        zend_refcounted  *counted;          //引用计数
        zend_string      *str;              //字符串类型
        zend_array       *arr;              //数组类型
        zend_object      *obj;              //对象类型
        zend_resource    *res;              //资源类型
        zend_reference   *ref;              //引用类型
        zend_ast_ref     *ast;              //抽象语法树
        zval             *zv;               //zval类型
        void             *ptr;              //指针类型
        zend_class_entry *ce;               //class类型
        zend_function    *func;             //function类型
        struct {
            uint32_t w1;
            uint32_t w2;
        } ww;
    } zend_value;
    

    变量弱类型的实现

    在zval中,value存储具体的值或者值的指针,u1存储变量类型的相关信息,u2则存储优化信息。php根据u1.type的值的变化,取value中对应类型的值的指针,从而完成变量类型的表达。
    在上述php代码中

    • $a=1;php生成新的zval,将zval.u1.v.type定义为整型,将1写入到zval.value.lval中。
    • echo $a;PHP在变量哈希表中,找到变量名为a的键,拿到对应的zval,根据zval.u1.v.type的类型读取对应zval.value中的值或者是对应的指针类型。
    • $a='aaa';php拿到变量a的zval,根据要赋予变量的值,来修改zval.u1.v.type的值,同时将zval.value中对应的存储字段进行赋值。

    变量类型的转换

    在php内核中定义了几个函数,当需要将变量转换成相同类型时,则会取调用对应的函数,进行变量的转换。比如在比较整型和字符串时,会根据规则,将整型转成字符串,或者是将字符串转成整型,在进行比较。

    这里贴一下_zval_get_string_func 转换成字符串函数的源码

    ZEND_API zend_string* ZEND_FASTCALL _zval_get_string_func(zval *op) /* {{{ */
    {
    try_again:
        switch (Z_TYPE_P(op)) {
            case IS_UNDEF:
            case IS_NULL:
            case IS_FALSE:
                return ZSTR_EMPTY_ALLOC();//被初始化成空字符串
            case IS_TRUE:
                if (CG(one_char_string)['1']) {
                    return CG(one_char_string)['1'];
                } else {
                    return zend_string_init("1", 1, 0);
                }
            case IS_RESOURCE: {//返回对应的资源号ID
                char buf[sizeof("Resource id #") + MAX_LENGTH_OF_LONG];
                int len;
    
                len = snprintf(buf, sizeof(buf), "Resource id #" ZEND_LONG_FMT, (zend_long)Z_RES_HANDLE_P(op));
                return zend_string_init(buf, len, 0);
            }
            case IS_LONG: {
                return zend_long_to_str(Z_LVAL_P(op));
            }
            case IS_DOUBLE: {
                return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
            }
            case IS_ARRAY:
                zend_error(E_NOTICE, "Array to string conversion");
                return zend_string_init("Array", sizeof("Array")-1, 0);
            case IS_OBJECT: {
                zval tmp;
                if (Z_OBJ_HT_P(op)->cast_object) {
                    if (Z_OBJ_HT_P(op)->cast_object(op, &tmp, IS_STRING) == SUCCESS) {
                        return Z_STR(tmp);
                    }
                } else if (Z_OBJ_HT_P(op)->get) {
                    zval *z = Z_OBJ_HT_P(op)->get(op, &tmp);
                    if (Z_TYPE_P(z) != IS_OBJECT) {
                        zend_string *str = zval_get_string(z);
                        zval_ptr_dtor(z);
                        return str;
                    }
                    zval_ptr_dtor(z);
                }
                zend_error(EG(exception) ? E_ERROR : E_RECOVERABLE_ERROR, "Object of class %s could not be converted to string", ZSTR_VAL(Z_OBJCE_P(op)->name));
                return ZSTR_EMPTY_ALLOC();
            }
            case IS_REFERENCE:
                op = Z_REFVAL_P(op);
                goto try_again;
            case IS_STRING:
                return zend_string_copy(Z_STR_P(op));
            EMPTY_SWITCH_DEFAULT_CASE()
        }
        return NULL;
    }
    

    当你在php代码中,在一个值前(string) strval()或者是使用echo,将变量当作字符串处理时,就会用到这个函数。

    • 当变量类型是未定义undefined、空null或者是布尔false时,返回一个初始化的空字符串。
    • 当变量类型是布尔true,返回'1'
    • 当变量类型是资源,则返回 'Resource id #'+资源id
    • 当变量类型为整型,则返回这一串数字的字符串
    • 当变量类型为浮点数,则返回这一串数字的字符串
    • 当变量类型为数组,则返回字符串'Array'
    • 当变量类型为对象,不可转换,并报错 'Object of class %s could not be converted to string'
    • 当变量类型为引用,取出引用的zval,在继续转换
    • 当变量类型为字符串,则输出字符串。

    类似的函数还有 zval_get_double、zval_get_long

    变量的存储

    php的变量存储在hashtable上,当调用一个函数或者类的时候,会创建新的hashtable,这也就是为什么没法直接在函数内使用外部定义的变量。因为它们分属两个作用域,一个是当前的(局部变量),一个是全局的(全局变量)。在函数内怎么使用全局变量呢,通过globals 关键字,在创建新的hashtable的时候,将局部变量表的值链接全局变量表,从而达到了,在函数内使用全局变量。

    当在函数中使用global $a

    变量的赋值和引用

    赋值

    对于整型、浮点型、布尔和NULL,由于占用空间小,在zval中直接存储。直接在进行赋值时,会创建2个zval。
    字符串、数组、资源类型和对象会在赋值时,指向同一个value,等到变量的值被改变时,才会申请变量值的内存空间。

    $a='1234';
    $b=$a;
    

    当将a的值赋给b时,变量a和b指向同一个字符串,并且zend_string的refcount 会记录当前被引用的次数。

    字符串赋值

    引用

    在php7中,引入了zend_reference来处理。使得即使变量同时被引用和赋值,在内存中只存有一份字符串。
    通过在zend_reference中的zval来进行一次引用的转发。

    struct _zend_reference{
      zend_refcounted_h gc;
      zval val;
    }
    
    $a = '1234';//$a->zend_string(type=IS_STRING,recount_gc=1,is_ref_gc=0);
    $c=$a;// $c,$a-> zend_string (type=IS_STRING,recount_gc=2,is_ref_gc=0);
    $b=&$a;// $b,$a-> zval (type=IS_REFERENCE,recount_gc=2);
    //$c-> zend_string (type=IS_STRING,recount_gc=2,);
    
    字符串引用

    相关文章

      网友评论

          本文标题:php变量的基本实现

          本文链接:https://www.haomeiwen.com/subject/oazxkqtx.html