6

以下のコードは、PHP 参照の奇妙な動作を示しています。

<?php

function this_works()
{
    $root = array('name'=>'root', 'children'=>array());
    $level_1 = array('name'=>'level_1', 'children'=>array());
    $item1 = array('name'=>'level_2_1', 'children'=>array());
    $item2 = array('name'=>'level_2_2', 'children'=>array());

    $croot = &$root;

    $croot['children'][] = &$level_1;

    $croot = &$level_1;

    $croot['children'][] = &$item1;
    $croot['children'][] = &$item2;

    $croot = &$root;

    print_r($croot);
}    

function this_fails()
{    
    $root = array('name'=>'root', 'children'=>array());
    $level_1 = array('name'=>'level_1', 'children'=>array());
    $item1 = array('name'=>'level_2_1', 'children'=>array());
    $item2 = array('name'=>'level_2_2', 'children'=>array());
    $croot = &$root;

    $stack = array();

    $croot['children'][] = &$level_1;
    $crootref = &$croot;

    array_push($stack, $crootref);

    $croot = &$level_1;

    $croot['children'][] = &$item1;
    $croot['children'][] = &$item2;

    # this works, assignment below - doesn't... WHY?
    #$x = array_pop($stack);
    #var_dump($x);

    $croot = array_pop($stack);

    print_r($croot);
}    

this_works();
echo "------------------\n";
this_fails();

?>

最初の関数は期待される結果を提供しますが、2 番目の関数は失敗し、再帰ループについて主張します。

Array
(
    [name] => root
    [children] => Array
        (
            [0] => Array
                (
                    [name] => level_1
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [name] => level_2_1
                                    [children] => Array
                                    (
                                    )

                                )

                            [1] => Array
                                (
                                    [name] => level_2_2
                                    [children] => Array
                                        (
                                        )

                                )

                           )

                )

        )

)
------------------
Array
(
    [name] => root
    [children] => Array
        (
            [0] => Array
                (
                    [name] => root
                    [children] => Array
 *RECURSION*
                )

    )

)

奇妙なことに、2 番目の関数で中間変数を使用してスタックから値を取得すると、結果は再び OK になります。何が起こっているのかわかりません。1 つの assinment により、ルート要素をそれ自体の子として何度も取得するにはどうすればよいですか?

もともと、XML からツリーを構築する必要があり (sax パーサーを使用)、現在のレベルのツリー ノードを指す「現在のルート」を作成し、それをスタックにプッシュ/ポップして子要素を追加するつもりでしたが、驚くべきことに、上記の 2 つの関数で示された問題により、このスキームを実装できませんでした。

では、そのようなアプローチの何が問題なのですか?

4

3 に答える 3

0

1枚の写真は1000語に相当します。何が起こっているのかを正確に理解するのに少し時間がかかりました。

Xdebugを使用して内部データを適切にダンプし、参照数とコピーオンライトの影響を確認する必要がありました。

最初の投稿のコードの問題は、その割り当てです

$ croot = array_pop($ stack);

crootが'level_1'の場合、コピーによって実行されます。つまり、crootの要素(level_1と同じ)にスタックからのデータが入力され、この操作後のcrootは元のルートと同じではありません。

画像はよりよく説明します。

メモリ内のPHP変数

于 2012-10-10T19:54:46.010 に答える
0

これは、PHPが配列内で参照を行う奇妙な方法が原因です。右側の参照を使用して通常の(参照ではない)割り当てを実行しても、通常は左側が参照になりませんが、配列内の参照は、参照演算子がなくても割り当てで保持されます。

何が起こっているのかを説明するために、以下のコードにコメントしました。これを正しく説明したと思います。午前1時で、疲れています。

$root = array('name'=>'root', 'children'=>array());
$level_1 = array('name'=>'level_1', 'children'=>array());
// $croot and $root now point to the same variable
$croot = &$root;

$stack = array();

$croot['children'][] = &$level_1;
// $crootref, $croot and $root all now point to the same variable
$crootref = &$croot;
// $stack[0], $crootref, $croot and $root all now point to the same variable.
// $stack[0]['children'][0], $level_1, $croot['children'][0] point to the same variable
array_push($stack, $crootref);
// $croot, $level_1 and $stack[0]['children'][0] now point to the same variable
// Infinite loop is caused as $stack[0]['children'][0] is now an alias for $croot
// which contains $croot['children'][0] which is an alias for $stack[0]['children'][0]
// which is an alias for $croot which contains....
$croot = &$level_1;
于 2012-10-04T23:52:33.357 に答える
0

array_pop()の代わりにunset()を使用する、単なる回避策。また、array_pop()を使用すると、入力配列の配列ポインターがリセットされるため、reset()を使用して同じ結果をシミュレートします。

<?php

function this_fails_fixed()
{    
    $root = array('name'=>'root', 'children'=>array());
    $level_1 = array('name'=>'level_1', 'children'=>array());
    $item1 = array('name'=>'level_2_1', 'children'=>array());
    $item2 = array('name'=>'level_2_2', 'children'=>array());
    $croot = &$root;

    $stack = array();

    $croot['children'][] = &$level_1;
    $crootref = &$croot;

    array_push($stack, $crootref);

    $croot = &$level_1;

    $croot['children'][] = &$item1;
    $croot['children'][] = &$item2;

    unset($croot[count($croot)-1]);
        reset($croot);

    print_r($croot);
}    

this_fails_fixed();

?>
于 2012-10-04T23:53:11.243 に答える