首先需要知道的是:
拿$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);
}