程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> PHP綜合 >> php foreach使用引用的陷阱

php foreach使用引用的陷阱

編輯:PHP綜合

最近工作中在foreach中使用引用的時候出現一個怪現象,使用2次foreach的時候數組值發生了改變,代碼示例如下

 <?php
 $arr = array('1','2','3');
 foreach($arr as &$row){
 }
 foreach($arr as $row){
 }

 

我的預期結果是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做比喻那就是快捷方式)。


看到這兒,在手冊上又發現了一段代碼

 <?php
 $array1 = array(1,2);
 $x = &$array1[1]; // Unused reference
 $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]的值也一起修改了.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved