なぜPHPのforeachは配列のポインタを一度進めますか?

これはPHP内で foreach が実装されている背景にある好奇心の問題です。

検討してください:

$arr = array(1,2,3);
foreach ($arr as $x) echo current($arr) . PHP_EOL;

出力される:

2
2
2

私は foreach が配列ポインタを先頭に巻き戻すことを理解しています。しかし、なぜそれを1回だけ増やすのでしょうか?魔法の箱の中で何が起こっているのですか?これはちょうど(醜い)人工物ですか?


Thanks @NickC -- for anyone else curious about zval and refcount, you can read up on the basics here

18
そのコードはどのように機能しますか? $ arrはどこにも定義されていないように見えます。
追加された 著者 GordonM,
foreach は配列のコピーに対して動作します。私はなぜそれが実際にすべての配列ポインタを変更するか分からない。
追加された 著者 Boann,
Ha - yesループを最適化しようとしていましたが、重要な部分を取り出しました。
追加された 著者 jlb,
@Boann、はい、コアでは何かが最適化されていない(またはハッキングでハイパーに最適化されている)ように思えるでしょう
追加された 著者 jlb,
@edorian:実際には未定義の動作の技術的説明を求めています。しかしそれはあまり面白くしない。提案されたリンクは、明らかにベストではなかった(今日のクローズアップでは驚くほど急いでいる)。私たちはこれについてより良い議論をしました。どこかに... - これ以上の説明は必要ありません。
追加された 著者 mario,
@エドリアン:はい、興味深い質問です!私はそれがバグだと思う傾向がありますが、私は間違っているかもしれません。なぜこれが起こっているのか、手がかりはありますか?
追加された 著者 Tadeck,
@ドリアン:同意。それは重複しているように見えましたが、実際にはもっと面白い質問です。より洗練されたものであり、解決策を得る代わりに説明を得ることを目指しています。
追加された 著者 Tadeck,
エコー電流($ arr)は何ですか? foreachループで$ arrを使用していません。 foreach($ arr $ x)echo current($ arr).PHP_EOL;
追加された 著者 Cyclonecode,
@Boannは、foreachが配列コピーで動作することを説明しているドキュメントを教えてくれますか?私はそうは思わない。
追加された 著者 SteAp,
これは複製ではありません。リンクされた質問は "foreachループの中で配列の内容を行いましたが、すべてが壊れていますか?<?go"> ビヘイビア "
追加された 著者 edorian,
@ Tadeck NikiCからの答えは私にとってうまくいくようだ
追加された 著者 edorian,
私はそれがコピーで動作すると思ったので 1 1 1 を生成することを期待していました。しかし、その後、私は de.php.net/manual/en/control- structures.foreach.php http://nikic.github.com/2011/11/11/PHP-Internals-When-does-f‌ oreach-copy.html 出力が 1 2であったはずです3 または 1 1 1 ではなく 2 2 2 非常に良い質問!
追加された 著者 edorian,
興味深いことに、質問は foreach に焦点を当てていますが、答えは current 関数の動作に依存しているようです。
追加された 著者 J. Bruni,

3 答え

最初の反復の直前に、 $ array foreach で使用するために "ソフトコピー"されます。つまり、実際のコピーは行われませんが、 $ array のzvalの refcount だけが 2 に増加します。

最初の繰り返し:

  1. The value is fetched into $x.
  2. The internal array pointer is moved to the next element, i.e. now points to 2.
  3. current is called with $array passed by reference. Due to the reference PHP cannot share the zval with the loop anymore and it needs to be separated ("hard copied").

次の反復では、 $ array zvalはもはや foreach zvalにはもう関係しません。したがって、配列ポインタはもう変更されず、 current は常に同じ要素を返します。

ちなみに、私は foreachコピー動作。文脈に関心があるかもしれませんが、主にハードコピーについて語っているので、この問題には直接関係しません。

15
追加された
@ NikiC、最初の段落で、あなたはrefcountが2に増加したと言った。しかし、私のテストは3のrefcountを示しています。それはなぜですか? stackoverflow.com/q/18158487/632951 をご覧ください。
追加された 著者 Pacerier,
良いことを知って、thx!関数のパラメータに独自のシンボル(関数本体で使用されているものと分離されている)があるかどうかは、すでに不思議に思っていた...
追加された 著者 Philippe Gerber,
@NikiC current()の前にrefcountが3になります。私はPHPのソースは分かりませんが、私の推測は次のようになります:1.元の$ arr宣言2. foreach()の$ arr - head 3. foreach() - 本体の$ arr。 :S
追加された 著者 Philippe Gerber,
私は、 foreach -head( foreach($ arr ... )内の$ arrのために refcount
追加された 著者 Philippe Gerber,
@NikiCありがとう。 1つのフォローアップ:なぜ内部配列ポインタが動かされるのでしょうか?これは$ xに値を取得する副産物ですか?
追加された 著者 jlb,
追加の括弧はどのように解析エラーを起こさないのでしょうか?
追加された 著者 Mark Tomlin,
最初の反復時には、reset + nextを実行します。それは文書化されています。しかし、refでiterationを欺くとループ内でreset()を使用していましたが、できませんでした。私はそれがそれに対する保護だと思う。
追加された 著者 hakre,
PHP 5.2.2と5.2.4の間の変更点まで、予期しない/未定義の動作の探求を絞り込むことができると思います。に関するすべての参照と関係している可能性があります。修正されたバグ#41372(ソース配列の内部ポインタがリセットされます - すべてのバージョンがOPの例で 1 1 1 を返す前に、
追加された 著者 mario,
@MarkTomlin foreachはそれを変数の代わりに式として扱うので、そうではありません。私は確かにhakreが答えにそれを加えた理由を正確には分かっていない。
追加された 著者 NikiC,
@PhilippeGerberはい、ループ内のdebug_zval_dump(現在まで)はrefcount 4を返します。したがって、ループの開始後に3にする必要があります。あなたは追加の参照がどこから来るのか知っていますか?私はFE_RESETにADDREFが1つしか見つかりませんでした
追加された 著者 NikiC,
@PhilippeGerberちょうどチェックし、refcountが5.3から5.4に減少したように見えます。 5.3はrefcount(3)を持ち、5.4はrefcount(2)を持っています。 (関数呼び出しのために、常にdebug_zval_dump refcountを-1にする必要があります)。
追加された 著者 NikiC,

私たちが少しだけコードを変更した場合、どのように面白いか見てみましょう:

$arr = array(1,2,3);
foreach ($arr as &$x) echo current($arr) . PHP_EOL;

我々はこの出力を得た:

2
3

いくつかの興味深い参照:

http://nikic.github.com /2011/11/11/PHP-Internals-When-does-foreach-copy.html

http://blog.golemon.com/2007/01/youre -being-lied-to.html

さて、これを試してみてください:

$arr = array(1,2,3);
foreach ($arr as $x) { $arr2 = $arr; echo current($arr2) . PHP_EOL; }

出力:

2
3
1

これは本当に興味深いです。

そして、これはどうですか?

$arr = array(1,2,3);
foreach ($arr as $x) { $arr2 = $arr; echo current($arr) . '/' . current($arr2) . PHP_EOL; }
echo PHP_EOL;
foreach ($arr as $x) { $arr2 = $arr; echo current($arr2) . '/' . current($arr2) . PHP_EOL; }

出力:

2/2
2/2
2/2

2/2
3/3
1/1

現在の関数への引数として配列を渡すとき、現在の関数が参照によって渡されるため、NickCの回答と同様に + 、内部の何かが、引数として渡された配列を変更します...

3
追加された
@ゴードン問題はない! :-)
追加された 著者 J. Bruni,

これはPHP 5.3でのコードオペコード解析の結果です。

See this example : http://php.net/manual/en/internals2.opcodes.fe-reset.php

オペレーション数:15 コンパイルされた変数:!0 = $ arr、!1 = $ x

line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   2     0  >   INIT_ARRAY                                       ~0      1
   1      ADD_ARRAY_ELEMENT                                ~0      2
   2      ADD_ARRAY_ELEMENT                                ~0      3
   3      ASSIGN                                                   !0, ~0
   3     4    > FE_RESET                                   $2      !0, ->13
   5  > > FE_FETCH                                         $3      $2, ->13
   6  >   ZEND_OP_DATA                                             
   7      ASSIGN                                                   !1, $3
   8      SEND_REF                                                 !0
   9      DO_FCALL                                      1          'current'
  10      CONCAT                                           ~6      $5, '%0A'
  11      ECHO                                                     ~6
  12    > JMP                                                      ->5
  13  >   SWITCH_FREE                                              $2
  14    > RETURN                                                   1

詳細については、NikiCの回答を参照してください。しかし、8行目では!0はループ内で決して変化しません。(5-12)

1
追加された
PHP - 日本のコミュニティ [ja]
PHP - 日本のコミュニティ [ja]
4 参加者の

このグループではPHPについて話します。 パートナー:kotaeta.com