变量赋值、销毁与作用域

变量赋值、销毁与作用域

标签(空格分隔): 未分类

获取左右值

在PHP内没有对于变量的声明操作,在赋值的时候同时完成声明。
例:$a = 10;
通过VLD查看其生成中间代码为ASSIGN.执行函数为ZEND_ASSIGN_SPEC_CV_CONST.在这个函数中获取左值和右值的代码为:

val *value = &opline->op2.u.constant;
zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX(Ts), 
                                                BP_VAR_W TSRMLS_CC);

右值为一个值,直接取操作数存储的constant字段;
左值是通过 _get_zval_ptr_ptr_cv函数获取zval值。

static zend_always_inline zval **_get_zval_ptr_ptr_cv(const znode *node, const temp_variable *Ts, int type TSRMLS_DC)
{
    zval ***ptr = &CV_OF(node->u.var);  

    //判断是否存在于EX(CVS)
    if (UNEXPECTED(*ptr == NULL)) {     

        //通过HastTable操作在EG(active_symbol_table)表中查找变量
        return _get_zval_cv_lookup(ptr, node->u.var, type TSRMLS_CC);
    }
    return *ptr;
}

// 函数中的CV_OF宏定义
#define CV_OF(i)     (EG(current_execute_data)->CVs[i])

赋值

情况一:赋值的左值存在引用(即zval变量中is_ref__gc字段不为0),并且左值不等于右值
- 判断左值是不是已经被引用过了;
- 左值已经被引用,则不改变左值的引用计数,将右值赋与左值;

if (PZVAL_IS_REF(variable_ptr)) {
    if (variable_ptr!=value) {
        zend_uint refcount = Z_REFCOUNT_P(variable_ptr);

        garbage = *variable_ptr;
        *variable_ptr = *value;
        Z_SET_REFCOUNT_P(variable_ptr, refcount);
        Z_SET_ISREF_P(variable_ptr);
        if (!is_tmp_var) {
            zendi_zval_copy_ctor(*variable_ptr);
        }
        zendi_zval_dtor(garbage);
        return variable_ptr;
    }
}

PZVAL_IS_REF(variable_ptr)判断is_ref__gc字段是否为0。在左值不等于右值的情况下执行操作。 所有指向这个zval容器的变量的值都变成了*value。并且引用计数的值不变。下面是这种情况的一个示例:

上面的例子的输出结果:

a:
(refcount=2, is_ref=1),int 10
a:
(refcount=2, is_ref=1),int 20

情况二:赋值的左值不存在引用,左值的引用计数为1,左值等于右值
在这个赋值过程中,$a的引用计数经历了一次加一和一次减一的操作。
情况三:赋值的左值不存在引用,左值的引用计数为1,右值存在引用
这里的$c = $a;的操作就是我们所示的第三种情况。对于这种情况,ZEND内核直接创建一个新的zval容器,左值的值为右值,并且左值的引用计数为1。
情况四:赋值的左值不存在引用,左值的引用计数为1,右值不存在引用
情况五:赋值的左值不存在引用,左值的引用计数为大于0,右值存在引用,并且引用计数大于0

变量的销毁

程序会先获取目标符号表,这个符号表是一个HashTable,然后将我们需要unset掉的变量从这个HashTable中删除。 如果对HashTable的元素删除操作成功,程序还会对EX(CVs)内存储的值进行清空操作。 以缓存机制来解释,在删除原始数据后,程序也会删除相对应的缓存内容,以免用户获取到脏数据。

变量的作用域

变量分为全局变量和局部变量,php内核实现方式:
对于全局变量,Zend引擎有一个_zend_executor_globals结构,该结构中的symbol_table就是全局符号表, 其中保存了在顶层作用域中的变量。同样,函数或者对象的方法在被调用时会创建active_symbol_table来保存局部变量
当程序在顶层中使用某个变量时,ZE就会在symbol_table中进行遍历,同理,如果程序运行于某个函数中,Zend引擎会遍历查询与其对应的active_symbol_table,而每个函数的active_symbol_table是相对独立的,由此而实现的作用域的独立。
函数中的局部变量就存储在_zend_execute_data的symbol_table中,在执行当前函数的op_array时, 全局zend_executor_globals中的*active_symbol_table会指向当前_zend_execute_data中的*symbol_table。因为每个函数调用开始时都会重新初始化EG(active_symbol_table)为NULL, 在这个函数的所有opcode的执行过程中这个全局变量会一直存在,并且所有的局部变量修改都是在它上面操作完成的,如前面的赋值操作等。而此时,其他函数中的symbol_table会存放在栈中,将当前函数执行完并返回时,程序会将之前保存的zend_execute_data恢复, 从而其他函数中的变量也就不会被找到,局部变量的作用域就是以这种方式来实现的。
所以,变量的作用域是使用不同的符号表来实现的,于是顶层的全局变量在函数内部使用时, 需要先使用global语句来将变量“挪”到函数独立的*active_symbol_table中, 即变量的跨域操作。

函数返回引用

PHP的函数返回引用不仅在函数定义的时候要说明返回的是引用,在调用的时候也要说明以引用的方式调用,缺一不可。
变量赋值、销毁与作用域_第1张图片

  • 当函数直接返回时会将要返回的变量内存结构复制一份成匿名结构体(注意此时引用计数refcount为0),然后这里无论使用=还是=&调用函数结果都是将$b指向这个结构体;
  • 当函数引用返回时会将要返回的变量内存结构的指针,这时候使用=调用函数结果和直接赋值$a=$b一样,当使用=&调用函数时和引用赋值$a=&$b表现一样。
  • 函数返回方式决定了对要返回的变量的处理方式(复制内存还是直接返回内存指针),函数调用方式决定了待赋值量和返回量的结合方式(指向同一内存结构还是绑定。

参考

深入PHP中的引用

你可能感兴趣的:(PHP,函数)