在php内部复制数组我们经常会这么写:
zval
*
conf
=
NULL
;
if
(
zend_parse_parameters
(
ZEND_NUM_ARGS
()
TSRMLS_CC
,
"a"
,
&
conf
)
==
FAILURE
)
{
return
;
}
zval
tmp
;
tmp
=
*
conf
;
zval_copy_ctor
(
&
tmp
);
这段逻辑很简单,接受一个用户传进来的数组参数,然后将其复制一份给tmp这个变量。
我们注意这句:
tmp = *conf; 这其实就是c语言的结构体变量赋值和下面这段是一样的意思:
struct
xinhua
{
int
a
;
int
b;
}
A
,
B;
A
=
B;
记得上学的时候谭浩强老人家说这样的结构体变量赋值是不行的,坑!而php手册里面zval复制介绍这块用大量的篇幅描述为“=”号的运算符重载,对于新学者还是会有蒙圈的感觉,会认为这个tmp=*conf;是运算符重载,个人觉得php手册描述的不妥。
赋值完毕后,tmp这个zval结构所有成员的值都和conf一样了包括ht这个指针的值。
btw:我们脑中一定有这样一个概念,那就是php里面所有的变量都是用zval表示,而数组在php内部是通过hashtable来表示的,zval通过ht这个指针指向这个hashtable。
现在我们通过gdb打断点来查看tmp和conf的ht指针。
我们看到ht指针的值是相同的,这验证了我们上面的说法,但是这显然有悖于我们数组复制的目的,现在该zval_copy_ctor出场了!
zval_copy_ctor最终会调用_zval_copy_ctor_func这个zend API我们直接看他的源码:
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
;
}
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
;
我们看到php通过
ALLOC_HASHTABLE_REL
(
tmp_ht
);重新在堆上分配了一个hashtable结构体,同时调用
zend_hash_init初始化hash表,紧接着调用
zend_hash_copy复制conf的hashtable,最后把复制好的hashtable地址赋值给我们最终的tmp的ht指针。
我们通过gdb查看下:
此时这tmp和conf这两个zval所引用的hashtable已经不是一个了。
我们再注意下
zend_hash_copy函数的
(
copy_ctor_func_t
)
zval_add_ref这块,其实
zend_hash_copy
的内部实现就是遍历hashtable,然后给每个元素都执行我们传进来的
zval_add_ref这个函数。
也就是说真正数组的元素并没有复制,而是添加引用计数这样的方式来避免性能损耗。
over ~