我的 PHP 引用数组“神奇地”变成了一个值数组……为啥?
Posted
技术标签:
【中文标题】我的 PHP 引用数组“神奇地”变成了一个值数组……为啥?【英文标题】:My PHP array of references is "magically" becoming an array of values... why?我的 PHP 引用数组“神奇地”变成了一个值数组……为什么? 【发布时间】:2012-01-10 07:15:01 【问题描述】:我正在围绕 mysqli 创建一个包装函数,这样我的应用程序就不必因为数据库处理代码而过于复杂。其中一部分是使用 mysqli::bind_param() 参数化 SQL 调用的代码。如您所知,bind_param() 需要引用。因为它是一个半通用的包装器,所以我最终打了这个电话:
call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs);
我收到一条错误消息:
Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given
上面的讨论是为了阻止那些会说“你的例子中根本不需要引用”的人。
我的“真实”代码比任何人都想阅读的要复杂一些,因此我将导致此错误的代码简化为以下(希望如此)说明性示例:
class myclass
private $myarray = array();
function setArray($vals)
foreach ($vals as $key => &$value)
$this->myarray[] =& $value;
$this->dumpArray();
function dumpArray()
var_dump($this->myarray);
function myfunc($vals)
$obj = new myclass;
$obj->setArray($vals);
$obj->dumpArray();
myfunc(array('key1' => 'val1',
'key2' => 'val2'));
问题似乎在于,在 myfunc() 中,在调用 setArray() 和调用 dumpArray() 之间,$obj->myarray 中的所有元素都不再是引用,而是变成了值。通过查看输出可以很容易地看到这一点:
array(2)
[0]=>
&string(4) "val1"
[1]=>
&string(4) "val2"
array(2)
[0]=>
string(4) "val1"
[1]=>
string(4) "val2"
请注意,此处输出的前半部分中的数组处于“正确”状态。如果这样做有意义,我可以在那时调用 bind_param(),它会起作用。不幸的是,输出的后半部分出现了问题。请注意数组值类型上缺少“&”。
我的推荐信怎么了?我怎样才能防止这种情况发生?当我真的不是语言专家时,我讨厌称其为“php bug”,但这可能是一个吗?这对我来说似乎很奇怪。我目前正在使用 PHP 5.3.8 进行测试。
编辑:
正如不止一个人指出的那样,解决方法是更改 setArray() 以通过引用接受其参数:
function setArray(&$vals)
我正在添加此注释以记录为什么这似乎有效。
一般来说,PHP,尤其是 mysqli,对于什么是“引用”似乎有一个有点奇怪的概念。观察这个例子:
$a = "foo";
$b = array(&$a);
$c = array(&$a);
var_dump($b);
var_dump($c);
首先,我确定您想知道为什么我使用数组而不是标量变量——这是因为 var_dump() 没有显示标量是否为引用的任何指示,但它确实用于数组成员。
无论如何,此时,$b[0] 和 $c[0] 都是对 $a 的引用。到目前为止,一切都很好。现在我们将第一把扳手投入工作:
unset($a);
var_dump($b);
var_dump($c);
$b[0] 和 $c[0] 仍然是对同一事物的引用。如果我们改变一个,两者仍然会改变。但它们指的是什么?内存中一些未命名的位置。当然,垃圾收集可确保我们的数据是安全的,并且会一直如此,直到我们停止引用它为止。
对于我们的下一个技巧,我们这样做:
unset($b);
var_dump($c);
现在 $c[0] 是对我们数据的唯一剩余引用。而且,哇!神奇的是,它不再是“参考”。不是 var_dump() 的度量,也不是 mysqli::bind_param() 的度量。
PHP manual 表示每条数据都有一个单独的标志“is_ref”。然而,这个测试似乎表明 'is_ref' 实际上等同于 '(refcount > 1)'
为了好玩,您可以将这个玩具示例修改如下:
$a = array("foo");
$b = array(&$a[0]);
$c = array(&$a[0]);
var_dump($a);
var_dump($b);
var_dump($c);
请注意,所有三个数组的成员都有引用标记,这支持了“is_ref”在功能上等同于“(refcount > 1)”的想法。
我无法理解为什么 mysqli::bind_param() 首先会关心这种区别(或者可能是 call_user_func_array()... 无论如何),但看起来我们“真正”需要确保的是在我们的 call_user_func_array() 调用中,$this->bindArgs 的每个成员的引用计数至少为 2(请参阅帖子/问题的开头)。最简单的方法(在这种情况下)是让 setArray() 传递引用。
编辑:
为了更多的乐趣和游戏,我修改了我的原始程序(此处未显示)以保留其等效于 setArray() 传递值,并创建一个免费的额外数组 bindArgsCopy,其中包含与 bindArgs 完全相同的内容.这意味着,是的,两个数组都包含对“临时”数据的引用,这些数据在第二次调用时被释放。正如上述分析所预测的那样,这是有效的。这表明上述分析不是 var_dump() 内部工作的产物(至少让我松了一口气),它还表明重要的是引用计数,而不是原始的“临时性”数据存储。
所以。我做出以下断言:在 PHP 中,出于 call_user_func_array() (可能更多)的目的,说数据项是“引用”与说项目的引用计数大于或等于 2 是一样的(忽略 PHP 对等值标量的内部内存优化)
Administrivia 注意:我很乐意将答案归功于 mario,因为他是第一个提出正确答案的人,但由于他是在评论中写的,而不是实际答案,所以我做不到它:-(
【问题讨论】:
setArray()
方法不是也需要获取数组作为参考参数吗?否则,您只是在创建对临时数组的引用。
@mario:先生,您说的完全正确,因为这确实解决了我的问题。这引出了一个问题……为什么这能解决问题?我希望对临时数组的引用仍然是引用,只是对由于引用本身而仅保存在内存中的东西的引用。如果我能找到这样的文档,我想我应该阅读 PHP 内存管理。
是的,将 &$vals 放入 setArray 函数中
很清楚为什么会发生这种情况。原始数组值对于第一个函数是本地的。一旦该函数完成,它就会被删除,您在该数组中的引用也会被删除。 PHP 很好,因为它会为您转换它们。参数中的 & 表示您通过引用传递值,并且它们在整个程序中仍然存在。有趣的是,您发现了这一点,但很清楚为什么会发生。
【参考方案1】:
将数组作为引用传递:
function setArray(&$vals)
foreach ($vals as $key => &$value)
$this->myarray[] =& $value;
$this->dumpArray();
我的猜测(在某些细节上可能是错误的,但希望在大多数情况下是正确的)为什么这会使您的代码按预期工作时,当您作为值传递时,调用 @987654322 一切都很酷@ 在setArray()
内部,因为对setArray()
内部的$vals
数组的引用仍然存在。但是当控制权返回到myfunc()
时,那个临时变量就和所有对它的引用一样消失了。因此,PHP 在为其释放内存之前,尽职尽责地将数组更改为字符串值而不是引用。但是如果你将它作为来自myfunc()
的引用传递,那么setArray()
正在使用对一个数组的引用,该数组在控制权返回myfunc()
时仍然存在。
【讨论】:
【参考方案2】:在参数签名中添加&
为我修复了它。这意味着该函数将接收原始数组的内存地址。
function setArray(&$vals)
// ...
CodePad.
【讨论】:
【参考方案3】:我刚刚在几乎完全相同的情况下遇到了同样的问题(我正在为自己的目的编写 PDO 包装器)。一旦没有其他变量引用相同的数据,我怀疑 PHP 正在更改对值的引用,而 Rick,您在编辑您的问题时的 cmets 证实了这种怀疑,所以谢谢您。
现在,对于遇到类似情况的任何其他人,我相信我有最简单的解决方案:只需在将数组传递给 call_user_func_array() 之前将每个相关数组元素设置为对其自身的引用。
我不确定内部到底发生了什么,因为这似乎不应该起作用,但是元素再次成为引用(您可以使用 var_dump() 看到),然后 call_user_func_array() 通过引用传递参数正如预期的那样。即使数组元素仍然是引用,此修复似乎也有效,因此您不必先检查。
在 Rick 的代码中,它会是这样的(bind_param 的第一个参数之后的所有内容都是引用,所以我跳过第一个并修复之后的所有参数):
for ($i = 1, $count = count($this->bindArgs); $i < $count; $i++)
$this->bindArgs[$i] = &$this->bindArgs[$i];
call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs);
【讨论】:
以上是关于我的 PHP 引用数组“神奇地”变成了一个值数组……为啥?的主要内容,如果未能解决你的问题,请参考以下文章
【PHP】值为1的string类型,转成int类型后值变成了0?