Подтвердить что ты не робот

Игра со ссылками

Я понимаю, почему

$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] = 37;
echo $a['ID']."\n";
echo $b."\n";
echo $c['ID']."\n";

выходы 37, 42, 37

а

$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$b = 37;
echo $a['ID']."\n";
echo $b."\n";
echo $c['ID']."\n";

выходы 37, 37, 37

В обоих случаях $b является ссылкой на $a['ID'], а $c является указателем на тот же объект, что и $a.

Когда $b изменяет $a['ID'] и $c['ID'] изменение, потому что назначение $b изменяет значение, на которое ссылается $a['ID'].

Когда $c['ID'] изменяется, новый int присваивается $a['ID'], $b больше не ссылается на $a['ID'].

Но это меня мешает

$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] &= 0;
$c['ID'] |= 37;
echo $a['ID']."\n";
echo $b."\n";
echo $c['ID']."\n";

(выходы 37, 37, 37)

Это определенное поведение? Я ничего не видел об этом в документации...

4b9b3361

Ответ 1

Возьмем этот код в качестве основы: (refcounting documentation)

$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;

xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');

Это дает:

a:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
  public 'ID' => (refcount=2, is_ref=1),int 42
b:
(refcount=2, is_ref=1),int 42
c:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
  public 'ID' => (refcount=2, is_ref=1),int 42

Как вы говорите: $a является объектом, $b является ссылкой $a['ID'] ($a['ID'] и $b: refcount=2, is_ref=1) и $c является копией в качестве ссылки (с PHP5), поэтому $c является ссылкой $a (теперь это тот же объект: refcount=2, is_ref=0)


Если мы делаем: $c['ID'] = 37;

Получаем:

a:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
  public 'ID' => (refcount=1, is_ref=0),int 37
b:
(refcount=1, is_ref=0),int 42
c:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
  public 'ID' => (refcount=1, is_ref=0),int 37

$c['ID'] присваивается новый int so = > ​​

$b становится независимым (refcount=1 и is_ref=0), а также $a['ID'] и $c['ID']

НО, поскольку $c и $a зависят, $a['ID'] и $c['ID'] принимают одно и то же значение 37.


Теперь возьмем базовый код, и мы сделаем следующее: $c['ID'] &= 0;

UPDATE: Неожиданно получаем:

a:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
  public 'ID' => (refcount=2, is_ref=1),int 0
b:
(refcount=2, is_ref=1),int 0
c:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
  public 'ID' => (refcount=2, is_ref=1),int 0

вместо: (if: $c['ID'] = $c['ID'] & 0;)

a:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
  public 'ID' => (refcount=1, is_ref=0),int 0
b:
(refcount=1, is_ref=0),int 42
c:
(refcount=2, is_ref=0),
object(ArrayObject)[1]
  public 'ID' => (refcount=1, is_ref=0),int 0

ArrayObject реализует ArrayAccess так:

Как сказано в комментарии и описанном здесь:

Прямая модификация - это та, которая полностью заменяет значение размера массива, как в $obj [6] = 7. Косвенная модификация, с другой стороны, только изменяет часть измерения или пытается назначить измерение по ссылке на другую переменную, как в $obj [6] [7] = 7 или $var = & $OBJ [6]. Приращения c++ и сокращениями - также реализованы таким образом, что требуется косвенная модификация.

Возможный ответ:

"Комбинированный оператор (+ =, - =, & =, | =) можно было бы работать одинаково (косвенная модификация.)":

refcount и is_ref не влияют поэтому (в нашем случае) значения всех связанные переменные. ($c['ID'] = > $a['ID'] = > $b)

Ответ 2

Это более или менее определенное (но иногда недокументированное) поведение; главным образом потому, что $a не является array, а <<22 > .

Сначала взглянем на ваш третий фрагмент кода:

$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] &= 0;

Последнее назначение означает:

$tmp = &$c->offsetGet('ID');
$tmp &= 0; // or: $tmp = $tmp & 0;

Здесь вызывается только offsetGet(), и она возвращает ссылку на $c['ID'], как указано в этот комментарий. Поскольку offsetSet() не вызывается, изменяется значение $b.

Btw, приращение (++) и оператор декремента (-) работают аналогично, no offsetSet() вызывается.

Различия

Это отличается от вашего первого примера:

$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] = 37;

Последний оператор имеет следующий эквивалент:

$c->offsetSet('ID', 37);

Прежде чем новое значение будет присвоено $c['ID'], предыдущее значение будет эффективно unset(); поэтому $b - единственная переменная, все еще удерживающая до 42.

Доказательство этого поведения можно увидеть, когда вы используете объекты вместо чисел:

class MyLoggerObj
{
        public function __destruct()
        {
                echo "Destruct of " . __CLASS__ . "\n";
        }
}

$a = new ArrayObject();
$a['ID'] = new MyLoggerObj();
$a['ID'] = 37;

echo $a['ID']."\n";

Вывод:

Destruct of MyLoggerObj
37

Как вы можете видеть, деструктор вызывается на MyLoggerObj; потому что в этом случае нет переменной, которая больше удерживает значение.

Bonus

Если вы попытаетесь выяснить, когда offsetGet() и offsetSet() вызывается расширением ArrayObject, вы будете разочарованы, узнав, что вы не можете правильно имитировать mixed &offsetGet($key);. Фактически, это меняет поведение ArrayObject таким образом, что невозможно доказать поведение этого класса.