研发场景:
写代码时,某一个变量值前后不一致。中间逻辑也没有对该变量重新赋值修改,是什么原因引起的呢?
问题描述:
我们经常接手一些项目功能的二次开发或新代码开发。有时候代码不规范、冗余,常常是一个函数在idea中就要好几屏的显示。
这时候碰到一个需要的变量 $data,在第一屏的时候是某一个值,中间一堆的 new()类处理、foreach() 循环处理...。终于到了第N屏,$data再次被调用。离奇的是 $data值前后不一致了,不可重复读了。
接下来我们开始排查是什么问题引起的这个bug,代码演示↓↓↓。
<?php
function foo(&$arr){
foreach ($arr as $key => $value){
if($key === 0){
$arr[$key] = 'watermelon';
}
}
//逻辑处理...
return $arr;
}
$data = ['apple','banana','tangerine'];
//各种逻辑判断...
//各种逻辑判断...
//各种逻辑判断...
$result = foo($data);
//print_r($data);exit();
//各种逻辑判断...
//各种逻辑判断...
//各种逻辑判断...
//$data = test2($data);
//此时将$data作为参数传递给另一个函数
(new fruit)->putOnShelf($data);
class fruit{
public function putOnShelf($data)
{
print_r($data);
}
}
可以看到,$data 初始变量值是一个数组 $data = ['apple','banana','tangerine']。
中间一堆逻辑处理...
到了 foo()函数,被程序猿A使用引用 “&”。并对新数组 “$arr” 进行了修改,注意此时并没有对 $data 有过重新赋值的处理。
中间一堆逻辑处理...
到了 fruit类,再次使用 $data变量时,来看下此时 $data的值有没有变化。
key为0的值被修改了!!
原因分析:
PHP引用变量指向同一个内存地址,修改引用变量,原变量也会受影响
我们通过 memory_get_usage() 函数分析下变量和内存的关系.
不使用引用变量分析:
<?php
$a = range(1, 100);
echo memory_get_usage();echo PHP_EOL;
//unset($a);
$b = $a;
echo memory_get_usage();echo PHP_EOL;
$b = range(1, 100);
echo memory_get_usage();echo PHP_EOL;
$a初始变量内存 ≈ $a赋值给$b内存 (忽略memory_get_usage函数本身会占一定内存)
$a重新声明值时,内存明显变大
这是因为PHP中存在 COW(Copy On Write)机制,$b 变量不会再开辟一个空间,只会指向 $a的内存地址。当 $b值重新变化时,$b开辟了一个新的内存空间;
使用引用变量分析:
<?php
$a = range(1, 100);
echo memory_get_usage();echo PHP_EOL;
//unset($a);
$b = &$a;
echo memory_get_usage();echo PHP_EOL;
$b = range(1, 100);
echo memory_get_usage();echo PHP_EOL;
$a 初始变量内存 ≈ $b引用$a内存 (忽略memory_get_usage函数本身会占一定内存)
$b重新声明值时,内存没变化!
也就是说,使用引用变量,$b 和 $a使用相同的指针指向同一个内存空间。$b 值变化时,没有开辟新的内存空间,而是在原内存上更改值。这也解释了修改 $b的值,$a值也跟着变化的问题。
还可以使用 xdebug_debug_zval() 函数进一步分析
<?php
$a = 1;
xdebug_debug_zval('a');
$b = &$a;
xdebug_debug_zval('b');
//unset($a);
$b = 2;
xdebug_debug_zval('b');
xdebug_debug_zval('a');
更直接的分析 $b引用变量 $a,修改 $b的值,影响 $a值。