PHP匿点源码之变量赋值与引用

首先需要知道的是:

拿$a=10举例

PHP变量的名称和值在内核中是保存在两个不同的地方的,

值[10]是通过一个与名字毫无关系的zval结构来保存

名字[a]则保存在符号表里,

两者之间通过指针联系着。


我们来看下zval的结构

/* 变量存储结构 */
struct _zval_struct {
	/* Variable information */
	zvalue_value value;		/* value */
	zend_uint refcount__gc;		/* 引用计数 */
	zend_uchar type;		/* active type 变量的类型 [IS_NULL, IS_BOOL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_RESOURCE]之一*/
	zend_uchar is_ref__gc;		/* 是否被引用 */
};
其中  is_ref__gc、refcount__gc 在变量共用内存空间是后发挥着巨大的作用, 例如:

<?php
$a = "hello";
$b = $a;
unset($a);
?>

当一个变量被第一次创建的时候,它对应的zval结构体的refcount__gc成员的值会被初始化为1,理由很简单,因为只有这个变量自己在用它。但是当你把这个变量赋值给别的变量时,refcount__gc属性便会加1变成2,因为现在有两个变量在用这个zval结构了!


这个时候当我们再用unset删除$a的时候,它删除符号表里的$a的信息,然后清理它的值部分,这时它发现$a的值对应的zval结构的refcount值是2,也就是有另外一个变量在一起用着这个zval,所以unset只需把这个zval的refcount减去1就行了!


好了变量的介绍先说到这儿, 更多可以参看这篇文章:http://blog.sina.com.cn/s/blog_75a2f94f0101gygh.html


现在我们就例子来分析:

<?php

$a = 1;
xdebug_debug_zval('a');
$b = &$a;
xdebug_debug_zval('a');
$c = 20;
$a = &$c;
xdebug_debug_zval('a');

var_dump('a:'.$a, 'b:'.$b, 'c:'.$c);

unset($a);
xdebug_debug_zval('a');

var_dump('a:'.$a, 'b:'.$b, 'c:'.$c);

?>

方法介绍

void xdebug_debug_zval( [string varname [, ...]] )
用于打印一个或多个变量结构的相关信息。包括变量类型,值以及变量引用次数。如果传递的是一个数组,则会递归数组中的所有元素。

结果



注释掉xdebug_debug_zval('a'); 查看opcode


由此我们可以推断出此处的处理函数:

static int ZEND_FASTCALL  ZEND_ASSIGN_REF_SPEC_CV_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
	USE_OPLINE
	zend_free_op free_op2;
	zval **variable_ptr_ptr;
	zval **value_ptr_ptr;

	SAVE_OPLINE();
	value_ptr_ptr = _get_zval_ptr_ptr_cv_BP_VAR_W(execute_data, opline->op2.var TSRMLS_CC);

	if (IS_CV == IS_VAR &&
	    value_ptr_ptr &&
	    !Z_ISREF_PP(value_ptr_ptr) &&
	    opline->extended_value == ZEND_RETURNS_FUNCTION &&
	    !EX_T(opline->op2.var).var.fcall_returned_reference) {
		if (free_op2.var == NULL) {
			PZVAL_LOCK(*value_ptr_ptr); /* undo the effect of get_zval_ptr_ptr() */
		}
		zend_error(E_STRICT, "Only variables should be assigned by reference");
		if (UNEXPECTED(EG(exception) != NULL)) {

			HANDLE_EXCEPTION();
		}
		return ZEND_ASSIGN_SPEC_CV_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
	} else if (IS_CV == IS_VAR && opline->extended_value == ZEND_RETURNS_NEW) {
		PZVAL_LOCK(*value_ptr_ptr);
	}
	if (IS_CV == IS_VAR && UNEXPECTED(EX_T(opline->op1.var).var.ptr_ptr == &EX_T(opline->op1.var).var.ptr)) {
		zend_error_noreturn(E_ERROR, "Cannot assign by reference to overloaded object");
	}

	variable_ptr_ptr = _get_zval_ptr_ptr_cv_BP_VAR_W(execute_data, opline->op1.var TSRMLS_CC);
	if ((IS_CV == IS_VAR && UNEXPECTED(value_ptr_ptr == NULL)) ||
	    (IS_CV == IS_VAR && UNEXPECTED(variable_ptr_ptr == NULL))) {
		zend_error_noreturn(E_ERROR, "Cannot create references to/from string offsets nor overloaded objects");
	}
	zend_assign_to_variable_reference(variable_ptr_ptr, value_ptr_ptr TSRMLS_CC);		//在这里执行分配的操作

	if (IS_CV == IS_VAR && opline->extended_value == ZEND_RETURNS_NEW) {
		Z_DELREF_PP(variable_ptr_ptr);
	}

	if (RETURN_VALUE_USED(opline)) {
		PZVAL_LOCK(*variable_ptr_ptr);
		AI_SET_PTR(&EX_T(opline->result.var), *variable_ptr_ptr);
	}


	CHECK_EXCEPTION();
	ZEND_VM_NEXT_OPCODE();
}

static void zend_assign_to_variable_reference(zval **variable_ptr_ptr, zval **value_ptr_ptr TSRMLS_DC)
{
	zval *variable_ptr = *variable_ptr_ptr;
	zval *value_ptr = *value_ptr_ptr;

	if (variable_ptr == &EG(error_zval) || value_ptr == &EG(error_zval)) {
		variable_ptr_ptr = &EG(uninitialized_zval_ptr);
	} else if (variable_ptr != value_ptr) {
		if (!PZVAL_IS_REF(value_ptr)) {						//此时右值不是一个引用
			/* break it away */
			Z_DELREF_P(value_ptr);			 				//refcount_gc减1的作用 是看 是否还有其他变量也使用了valu_ptr_ptr对应的zval,如果有,则重新分配zval
			if (Z_REFCOUNT_P(value_ptr)>0) {
				ALLOC_ZVAL(*value_ptr_ptr);
				ZVAL_COPY_VALUE(*value_ptr_ptr, value_ptr);
				value_ptr = *value_ptr_ptr;
				zendi_zval_copy_ctor(*value_ptr);
			}
			Z_SET_REFCOUNT_P(value_ptr, 1);					//因为上面减1了,所以这里要加1,
			Z_SET_ISREF_P(value_ptr);						//设置is_ref为1
		}

		*variable_ptr_ptr = value_ptr;						//将variable_ptr_ptr这个地址指针内容 为 1 的地址
		Z_ADDREF_P(value_ptr);								//还要将 refcount_gc加1

		zval_ptr_dtor(&variable_ptr);						//根据情况释放内存
	} else if (!Z_ISREF_P(variable_ptr)) {
		if (variable_ptr_ptr == value_ptr_ptr) {
			SEPARATE_ZVAL(variable_ptr_ptr);
		} else if (variable_ptr==&EG(uninitialized_zval)
			|| Z_REFCOUNT_P(variable_ptr)>2) {
			/* we need to separate */
			Z_SET_REFCOUNT_P(variable_ptr, Z_REFCOUNT_P(variable_ptr) - 2);
			ALLOC_ZVAL(*variable_ptr_ptr);
			ZVAL_COPY_VALUE(*variable_ptr_ptr, variable_ptr);
			zval_copy_ctor(*variable_ptr_ptr);
			*value_ptr_ptr = *variable_ptr_ptr;
			Z_SET_REFCOUNT_PP(variable_ptr_ptr, 2);
		}
		Z_SET_ISREF_PP(variable_ptr_ptr);
	}
}

ZEND_API void _zval_copy_ctor_func(zval *zvalue ZEND_FILE_LINE_DC)
{
	switch (Z_TYPE_P(zvalue) & IS_CONSTANT_TYPE_MASK) {
		case IS_RESOURCE: {
				TSRMLS_FETCH();

				zend_list_addref(zvalue->value.lval);
			}
			break;
		case IS_BOOL:
		case IS_LONG:
		case IS_NULL:
			break;
		case IS_CONSTANT:
		case IS_STRING:
			CHECK_ZVAL_STRING_REL(zvalue);
			if (!IS_INTERNED(zvalue->value.str.val)) {
				zvalue->value.str.val = (char *) estrndup_rel(zvalue->value.str.val, zvalue->value.str.len);
			}
			break;
		case IS_ARRAY:
		case IS_CONSTANT_ARRAY: {
				zval *tmp;
				HashTable *original_ht = zvalue->value.ht;
				HashTable *tmp_ht = NULL;
				TSRMLS_FETCH();

				if (zvalue->value.ht == &EG(symbol_table)) {
					return; /* do nothing */
				}
				ALLOC_HASHTABLE_REL(tmp_ht);
				zend_hash_init(tmp_ht, zend_hash_num_elements(original_ht), NULL, ZVAL_PTR_DTOR, 0);
				zend_hash_copy(tmp_ht, original_ht, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));
				zvalue->value.ht = tmp_ht;
			}
			break;
		case IS_OBJECT:
			{
				TSRMLS_FETCH();
				Z_OBJ_HT_P(zvalue)->add_ref(zvalue TSRMLS_CC);
			}
			break;
	}
}

#define CHECK_ZVAL_STRING_REL(z) \
	if (Z_STRVAL_P(z)[ Z_STRLEN_P(z) ] != '\0') { zend_error(E_WARNING, "String is not zero-terminated (%s) (source: %s:%d)", Z_STRVAL_P(z) ZEND_FILE_LINE_RELAY_CC); }

#define estrndup_rel(s, length)					_estrndup((s), (length) ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_CC)

ZEND_API char *_estrndup(const char *s, uint length ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
	char *p;
#ifdef ZEND_SIGNALS
	TSRMLS_FETCH();
#endif

	HANDLE_BLOCK_INTERRUPTIONS();

	p = (char *) _emalloc(length+1 ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
	if (UNEXPECTED(p == NULL)) {
		HANDLE_UNBLOCK_INTERRUPTIONS();
		return p;
	}
	memcpy(p, s, length);
	p[length] = 0;
	HANDLE_UNBLOCK_INTERRUPTIONS();
	return p;
}

static zend_always_inline void i_zval_ptr_dtor(zval *zval_ptr ZEND_FILE_LINE_DC)
{
	if (!Z_DELREF_P(zval_ptr)) {
		TSRMLS_FETCH();

		ZEND_ASSERT(zval_ptr != &EG(uninitialized_zval));
		GC_REMOVE_ZVAL_FROM_BUFFER(zval_ptr);
		zval_dtor(zval_ptr);
		efree_rel(zval_ptr);
	} else {
		TSRMLS_FETCH();

		if (Z_REFCOUNT_P(zval_ptr) == 1) {
			Z_UNSET_ISREF_P(zval_ptr);
		}

		GC_ZVAL_CHECK_POSSIBLE_ROOT(zval_ptr);
	}
}

ZEND_API void _zval_dtor_func(zval *zvalue ZEND_FILE_LINE_DC)
{
	switch (Z_TYPE_P(zvalue) & IS_CONSTANT_TYPE_MASK) {
		case IS_STRING:
		case IS_CONSTANT:
			CHECK_ZVAL_STRING_REL(zvalue);
			STR_FREE_REL(zvalue->value.str.val);
			break;
		case IS_ARRAY:
		case IS_CONSTANT_ARRAY: {
				TSRMLS_FETCH();

				if (zvalue->value.ht && (zvalue->value.ht != &EG(symbol_table))) {
					/* break possible cycles */
					Z_TYPE_P(zvalue) = IS_NULL;
					zend_hash_destroy(zvalue->value.ht);
					FREE_HASHTABLE(zvalue->value.ht);
				}
			}
			break;
		case IS_OBJECT:
			{
				TSRMLS_FETCH();

				Z_OBJ_HT_P(zvalue)->del_ref(zvalue TSRMLS_CC);
			}
			break;
		case IS_RESOURCE:
			{
				TSRMLS_FETCH();

				/* destroy resource */
				zend_list_delete(zvalue->value.lval);
			}
			break;
		case IS_LONG:
		case IS_DOUBLE:
		case IS_BOOL:
		case IS_NULL:
		default:
			return;
			break;
	}
}

#define efree_rel(ptr)							_efree((ptr) ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_CC)

ZEND_API void _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
	TSRMLS_FETCH();

	if (UNEXPECTED(!AG(mm_heap)->use_zend_alloc)) {
		AG(mm_heap)->_free(ptr);
		return;
	}
	_zend_mm_free_int(AG(mm_heap), ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值