php foreach引用,php foreach使用引用的陷阱

最近工作中在foreach中使用引用的时候出现一个怪现象,使用2次foreach的时候数组值发生了改变,代码示例如下

我的预期结果是1,2,3 但是实际结果输出1,2,2

奇怪了,遍历数组难道还会改变数组的值么,猜测原因肯定出现在&row这个引用上。

在第2个循环里打印$arr

Array

(

[0] => 1

[1] => 2

[2] => 1

)

Array

(

[0] => 1

[1] => 2

[2] => 2

)

Array

(

[0] => 1

[1] => 2

[2] => 2

)

解释下具体的执行流程

1.第1个foreach结束,$row成为$arr[3]的引用

2.第2个foreach循环第1次的时候$row=1,所以此时$arr[3]=1,所以此时$arr=array(1,2,1),

3.依次类推,第2个foreach循环结束的时候$row=2,所以此时$arr=array(1,2,2);

在琢磨这个代码的时候,又翻了下php手册对引用的解释: 在 PHP中引用意味着用不同的名字访问同一个变量内容,这并不像 C 的指针:例如你不能对他们做指针运算,他们并不是实际的内存地址。

最接近的比喻是 Unix 的文件名和文件本身——变量名是目录条目,而变量内容则是文件本身。引用可以被看作是 Unix 文件系统中的硬链接(如果拿windows做比喻那就是快捷方式)。

看到这儿,在手册上又发现了一段代码

$array2 = $array1; //reference now also applies to $array2 !

$array2[1]=22; //(changing [0] will not affect $array1)

print_r($array1);

Produces:

Array

(

[0] => 1

[1] => 22 // var_dump() will show the & here

)

结果又出乎意料,注释掉$x = &$array1[1],改变$array2的值不影响$arry1的值,联想到php垃圾回收的引用计数器,每个php变量存在于zval容器里,zval容器除了包含变量的值和类型,还有2个额外的信息,

第1个是is_ref,是个bool值,用来标识变量是否是引用集合,通过这个我就知道这个变量是否是普通变量或者引用变量啦,第2个额外字段是refcount,表示指向这个zval容器的变量个数。

zval结构如下

typedef struct_zval_struct zval;

...struct_zval_struct {/*Variable information*/zvalue_value value;/*value*/zend_uint refcount__gc;

zend_uchar type;/*active type*/zend_uchar is_ref__gc;

};

$a = 'ok';

xdebug_debug_zval('a');

a:(refcount=1, is_ref=0),string 'ok' (length=2)

$b = &$a;

xdebug_debug_zval('a');

xdebug_debug_zval('b');

a:(refcount=2, is_ref=1),string 'ok' (length=2)

b:(refcount=2, is_ref=1),string 'ok' (length=2)

这时,引用次数是2,因为同一个变量容器被变量a和变量 b关联.当没必要时,php不会去复制已生成的变量容器,变量容器在"refcount"变成0时就被销毁

由于php内置函数debug_zval_dump不能看到zval的is_ref信息,所以这里使用xdebug_debug_zval,你需要安装xdebug扩展。

$a = 'ok';$b = $a;$a = 'no';

xdebug_debug_zval('a');

xdebug_debug_zval('b');

$a最后的值毫无疑问变成了'no',php是怎么做的呢?

当执行$b=$a;的时候$a和$b指向了同一个zval容器,refcount=2,

最后改变$a的值的时候,会执行php的copy on write机制

PHP的copy on write机制:

php在修改一个变量以前,会检查refcount值,refcount大于1,php会复制一个新的zval,并将原来zval refcount减1,

并修改symbol_table(符号表),使得两个变量分离,这个机制就是所谓的copy on write(写时复制),

$a = 'ok';

$b = &$a;

$b = 'no';

$a,$b最后的值毫无疑问都变成了'no',

执行到第2行的时候$a, $b指向同一个zval容器,refcount=2,is_ref=1,这时回执行php的change on write机制

PHP的change on write机制:

is_ref=1的时候,php会修改zval的值,但不会复制zval,这个过程称作(change on write:写时改变)

回到最开始手册上那个数组赋值的问题,array1和$array2

$array1 = array(1,2);$x = &$array1[1]; //Unused reference

$array2 = $array1; //reference now also applies to $array2 !

//xdebug_debug_zval('x');

xdebug_debug_zval('array1');

xdebug_debug_zval('array2');print_r($array1);

(refcount=2, is_ref=0),

array (size=2)

0 => (refcount=1, is_ref=0),int 1

1 => (refcount=2, is_ref=1),int 2

array2:

(refcount=2, is_ref=0),

array (size=2)

0 => (refcount=1, is_ref=0),int 1

1 => (refcount=2, is_ref=1),int 2

Array ( [0] => 1 [1] => 2 )

可以看到$array1, $array2指向的是同一个zval,而且$array1[1], $array2[1],$x指向的也是同一个zval,

而且属性ref_count=1,所以修改$array2[1]的值的时候,$array1[1]的值也一起修改了.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值