php变量的基本实现

2018-12-17  本文已影响0人  code_nerd

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代码中

变量类型的转换

在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,将变量当作字符串处理时,就会用到这个函数。

类似的函数还有 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,);
字符串引用
上一篇下一篇

猜你喜欢

热点阅读