PHP 内核:foreach 是如何工作的(一)
foreach 是如何工作的?PHP 内核:foreach 是如何工作的(二) 首先声明,我知道 foreach 是什么,也知道怎么去用它。但这个问题关心的是,内核中 foreach 是如何运行的,我不想回答关于 “如何使用 foreach 循环数组” 的任何问题。 很长时间我都认为 foreach 是直接作用于数组本身,后来一些资料表明,它作用于数组的一个副本,那时我以为这就是真相了。但最近我又讨论了一下这件事,经过一些试验,发现我之前的想法并非完全正确。 ? 让我来展示一下我的观点。下面的测试用例中我们将使用以下数组: ?
测试用例 1:
这很清晰的表明我们不直接使用数据源 - 否则循环会一直持续下去,因此我们可以在循环中不停的推送元素到数组中。为了保证正确请看下面的测试用例: 测试用例 2:
这印证了我们的初步结论,在循环中使用的是数组的副本,否则我们将看到在循环中改变后的值。?但是... 如果我们查阅手册?手册,我们会发现下面这句话: 当 foreach 首次开始执行时,数组的内部指针自动重置为数组的第一个元素。 ? 没错。。。这似乎表明 foreach 依赖源数组的指针。但是我们刚刚证明我们 没有使用源数组,对吧?好吧,不完全是。 测试用例 3:
因此,尽管我们不支持使用源数组,但是直接使用源数组指针 - 指针位于循环结束时的数组末尾证明了这一点。除非这不是真的 - 如果是,那么?测试用例 1?将永远循环。 PHP 手册还说明: 由于 foreach 依赖与内部数组指针,因此在循环内部改变它可能导致意外的行为。 让我们找出那种 “意外行为” 是什么 (从技术上讲,任何行为都是意外的,因为我们也不知道将会发生什么)。 测试用例 4:
测试用例 5:
... 意料之中,事实上它似乎支持 「复制源」 理论。 问题 这是怎么回事呢?我的 C-fu 还不够好,不能通过简单地查看 PHP 源代码就得出正确的结论,如果有人能把它翻译成英语,我将不胜感激。 在我看来,
解答
在下面的讨论中,我将尝试准确的解释迭代在不同的场景中是如何工作的。到目前为止,最简单的例子是?
对于内部类,通过使用一个内部 API 来避免实际的方法调用,这个 API 本质上只是在 C 级对 Iterator 接口的映射。 数组和普通对象的迭代要复杂的多。首先,应该注意,在 PHP 中,“数组” 实际上是有序的字典,它们将按照这个顺序遍历(只要不使用形如 sort 一类的函数对其排序,它就能按照插入的顺序遍历)。这与按照键的自然顺序迭代(其他语言中的列表通常是如何排序的呢?)或者无序(其他语言中的字典通常是如何工作的呢)是截然不同的。 同样的情况也适用于对象,因为对象属性可以看做是另一个(有序的)字典,将属性名映射为对应的值,并加上一些可见性的操作处理。在大多数情况下,对象属性实际上并不是以这种低效的方式存储的。然而,如果你开始遍历一个对象,通常它将被打包转换为一个真正的字典。在这一点上,普通对象的迭代与数组的迭代非常相似(这就是为什么我在这没有过多讨论普通对象的迭代)。 到目前为止,一切顺利。遍历字典应该不难,对吧?当你意识到数组 / 对象可以在迭代期间更改时,问题就出现了。发生这种情况有如下几种: ?
在迭代期间允许修改的问题是删除当前所在元素的情况。假设你使用指针来跟踪你当前所在的数组元素。 如果现在释放了此元素,则会留下悬空指针(通常会导致 segfault 段错误) 有不同的方法来解决这个问题。 PHP 5 和 PHP 7 在这方面有很大不同,我将在下面描述这两种情况。 总结是 PHP 5 的方法相当愚蠢并导致出现各种奇怪的边缘情况问题,而 PHP 7 更复杂的方法导致出现更可预测和一致的行为情况。 初步得出结论,PHP 是使用引用计数和写时复制来管理内存。 这意味着如果你 “复制” 一个值,实际上只是复用其旧值并增加其引用计数(refcount)。 只有在执行某种修改后,才会执行其真正的副本(复制)。 请参阅 你被骗了,以获得有关此主题的更多的介绍。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |