17

先日、この一見非常に単純な質問に出くわしました $array1 を参照せずに $array2 の値を変更するにはどうすればよいですか? しかし、調べれば調べるほど、これが実際に意図したとおりに機能しているように見えたのは奇妙に思えました。この後、次の出力から生成されるオペコードを調べ始めました。

$array1 = array(2, 10);
$x = &$array1[1];
$array2 = $array1;
$array2[1] = 22;

echo $array1[1]; // Outputs 22

array2 は array1 のコピーのみであるべきであり、1 つの配列に発生したことは他の配列の内容に影響を与えるべきではないため、これは私にはおかしいようです。もちろん、2行目をコメントアウトすると、最終行は予想どおり 10 をエコーアウトします。

さらに調べてみると、Vulcan Logic Dumper を使用して PHP が生成するオペコードを示すクールなサイトがありました。上記のコードによって生成されたオペコードを次に示します。

Finding entry points
Branch analysis from position: 0
Return found
filename:       /in/qO86H
function name:  (null)
number of ops:  11
compiled vars:  !0 = $array1, !1 = $x, !2 = $array2
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   3     0  >   INIT_ARRAY                                       ~0      2
         1      ADD_ARRAY_ELEMENT                                ~0      10
         2      ASSIGN                                                   !0, ~0
   4     3      FETCH_DIM_W                                      $2      !0, 1
         4      ASSIGN_REF                                               !1, $2
   5     5      ASSIGN                                                   !2, !0
   6     6      ASSIGN_DIM                                               !2, 1
         7      OP_DATA                                                  22, $6
   8     8      FETCH_DIM_R                                      $7      !0, 1
         9      ECHO                                                     $7
        10    > RETURN                                                   1

これらのオペコードは、 http://php.net/manual/en/internals2.opcodes.phpにはあまり文書化されていませんが、英語ではオペコードが次のことを行っていると思います。一言で言えば…誰よりも私に向いているかもしれません。

  1. 行 3: 配列を最初の値で初期化し、$array1 に割り当てる前に 10 を追加します。
  2. 行 4: 書き込み専用を取得しますか? 値を配列から取得し、参照によって $x に割り当てます。
  3. 5 行目: $array1 を $array2 に設定します。
  4. 行 6: 1 の配列インデックスを取得します。$6 が返されることはありませんが、od_data はそれを 22 に設定していると推測しています。OD_DATA にはドキュメントがまったくなく、私が見たどこにもオペコードとしてリストされていません。
  5. 8 行目: $array1 のインデックス 1 から読み取り専用の値を取得し、それをエコーし​​ます。

オペコードを調べても、どこが間違っているのかわかりません。オペコードに関するドキュメントが不足していると感じています。また、オペコードを使用した経験がないため、これがどこで間違っているのかを理解できていない可能性があります。

編集1:

最初のコメントで Mike が指摘したように、配列の参照ステータスはコピー時に保持されます。ここでは、 http://php.net/manual/en/language.types.array.php#104064にリンクしている配列の記事の場所とともにドキュメントを見ることができます。この面白いことは警告とは見なされません。これが本当なら、私にとって驚くべきことは、あなたが期待するように、このコードの参照ステータスが保持されていないことです.

$array1 = array(2, 10);
$x = &$array1;
$array2 = $array1;
$array2[1] = 22;

echo $array1[1]; // Output is 10

したがって、これは参照によって単一の要素を割り当てようとした場合にのみ発生し、この機能がさらに混乱するようです。

php が個別に割り当てられた配列インデックスのステータスのみを保持するのはなぜですか?

編集2:

今日、HHVM を使用していくつかのテストを行いましたが、HHVM はコードの最初のスニップを処理します。私は PHP が大好きですが、HHVM は Zend Engine よりも優れているように見えます。

4

1 に答える 1

10

これについては、PHP マニュアル (見つけるのに必要以上の時間を費やさなければならない場合でも)、特にhttp://php.net/manual/en/language.types.array で説明されています。 php#104064

「共有」データは共有されたままになり、最初の割り当ては単にエイリアスとして機能します。...[] = ...インタプリタがそれらを発散リストとして扱い始めるような独立した操作で配列の操作を開始するまではそうではありません。それでも共有データは共有されたままであるため、共有された最初のn要素を持つが後続のデータが発散する 2 つの配列を持つことができます。

ある配列から別の配列への真の「値によるコピー」の場合、ほとんどの場合、次のようなことになります

$arr2 = array();
foreach($arr1 as $val) {
  $arr2[] = $val;
}

また

$arr2 = array();
for($i=count($arr1)-1; $i>-1; $i--) {
  $arr2[$i] = $arr[$i];
}

(主に逆ループを使用するのは、それができることであり、順ループよりも効率的であることを覚えている人が少ないためです =)

配列コピーの癖に対処するのに役立つ関数または何かがあると想像するでしょうが、そうでarray_copyはないようです。奇妙ですが、「PHP の状態」の 1 つです。過去に選択が行われ、結果として PHP はその選択にかなりの年月を費やしてきたので、それは単に「それらの 1 つ」にすぎません。不運にも!

于 2014-09-28T03:43:45.593 に答える