【问题标题】:How does PHP handle an array with a reference to itself as an element?PHP如何处理一个引用自身作为元素的数组?
【发布时间】:2013-10-31 08:42:11
【问题描述】:

假设我声明了一个数组:

$data = array( 'foo' => 'bar' );

现在我将添加对自身的引用作为新元素:

$data['baz'] = &$data;

转储$data 的内容会导致:

Array
(
[foo] => bar
[baz] => Array
    (
        [foo] => bar
        [baz] => Array
         *RECURSION*
    )

)

现在,我可以转储$data['baz']['baz']['baz']['baz']['baz']['baz']['baz']['baz']['baz'] 的内容,结果将与上面完全相同,因为数组有一个指向自身作为元素的指针。

我想知道的是 php 是否将数组作为一组数据处理,其指针与我在使用 $data 时调用的指针完全相同,或者它是否做一些完全不同的事情。

另外,PHP 在返回$data{['baz']*n} 的内容时会耗尽内存吗?

【问题讨论】:

  • 实际上,要理解这一点,您需要了解 PHP 数组是什么。它们不是列表或类似的东西,它们是散列,简而言之。所以 PHP 存储了一个引用,它是键。问题是在“递归”数组中,这样的引用指向它是另一个元素,仅此而已(即它仍然是一个引用,仅此而已)。而且 - 不,如果您在每次迭代时重新分配元素,您将不会出现内存溢出
  • 就像一个循环链表。它仅将内存用于单个循环,而不是用于所有副本的无限内存。
  • 数组没有“列”,列是您在将数组元素呈现为表格时选择的解释方式。
  • @meagar 这是真的。另外 - 虽然我对此有正确的想法,但我会等待能够以更正式和限制性的方式解释这一点的人:)

标签: php arrays recursion reference


【解决方案1】:

在 PHP 内部,所有内容都存储在称为 ZVALvariant 容器中。 $data 用一个 ZVAL 表示,$data 里面的每个键和每个值都是一个 ZVAL 等等。

所以在初始赋值之后,从 PHP 中创建了三个 ZVAL:

   /-------------------\      /-------------------\   
   | ZVAL #1           |  /==>| ZVAL #2           |   
   |   type: array     |  |   |   type: string    |   
   |   data: [         |  |   |   data: "foo"     |
   |      {            |  |   \-------------------/
   |          key: =======/                         /-------------------\
   |          val: ================================>| ZVAL #3           |
   |      }            |                            |   type: string    |
   |   ]               |                            |   data:  "bar"    |
   \-------------------/                            \-------------------/

注意:数组item的内部表示与上图不对应;我不想用不必要的细节给答案增加负担。出于同样的原因,ZVAL 的表示也被简化了。如果您想了解更多关于 PHP 内部的信息,请阅读源代码和/或this

您可以看到 "foo""bar" 被用作数组键/值对的事实无法通过查看它们的 ZVAL 来确定:您必须知道它们被数组。

在分配$data['baz'] = &$data 之后,你现在有了一个循环引用:在 ZVAL #1 的某个地方有一个指向 ZVAL #1 的指针:

   /-------------------\      /-------------------\   
   | ZVAL #1           |  /==>| ZVAL #2           |   
/=>|   type: array     |  |   |   type: string    |   
|  |   data: [         |  |   |   data: "foo"     |
|  |      {            |  |   \-------------------/
|  |          key: =======/                         /-------------------\
|  |          val: ================================>| ZVAL #3           |
|  |      },           |                            |   type: string    |
|  |      {            |                            |   data: "bar"     |
|  |          key: =========================\       \-------------------/
|  |          val: =========\               |       
|  |      }            |    |               |       /-------------------\
|  |   ]               |    |               \======>| ZVAL #4           |
|  \-------------------/    |                       |   type: string    |
|                           |                       |   data: "baz"     |
\===========================/                       \-------------------/

那么 PHP 是如何解析$data['baz']['baz'] 的呢?它知道$data 由 ZVAL#1 表示,并且它看到您正在尝试使用数组语法对其进行索引。它查看 ZVAL,发现它是一个数组,找到具有键 "baz" 的项目并获取代表它的 ZVAL。你知道什么?这又是 ZVAL#1。 $data['baz']的解析到此结束。

在下一步中,它发现您正在尝试将 $data['baz'] 作为数组进行索引。它知道$data['baz'] 由 ZVAL#1 表示,所以同样的事情最终会再次发生,依此类推。

您会注意到,上面的过程不涉及存储任何中间结果(第一步和第二步是完全独立的),这意味着 PHP 虚拟机在尝试解决问题时没有资源限制数组访问。

【讨论】:

  • 感谢您的详细解答。这正是我想要的。
  • 还有一个问题:你自己输入了ascii吗? ;)
  • @BobKruithof:很高兴能提供帮助,是的,我做到了 :-)
猜你喜欢
  • 1970-01-01
  • 2011-07-25
  • 2010-12-31
  • 1970-01-01
  • 2013-03-21
  • 1970-01-01
  • 2011-05-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多