Bug #80826 [Com]: Array member reference affecting pass by value assignment

From: Date: Thu, 04 Mar 2021 10:08:56 +0000
Subject: Bug #80826 [Com]: Array member reference affecting pass by value assignment
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-232533@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=80826&edit=1

 ID:                 80826
 Comment by:         tshumbeo at mailhouse dot biz
 Reported by:        tshumbeo at mailhouse dot biz
 Summary:            Array member reference affecting pass by value
                     assignment
 Status:             Not a bug
 Type:               Bug
 Package:            *General Issues
 Operating System:   Windows & Linux
 PHP Version:        7.4.15
 Block user comment: N
 Private report:     N

 New Comment:

> The common misconception about references is that references are "to" variables, and
> if you have $x=&$y that means $x refers "to" $y. But that's not true.

I know as much. But the foreach documentation explicitly notes a trivial case which does not
contradict this misconception (i.e. doesn't try to point it out):

  foreach (... as &$r) ...
  foreach (... as  $r) ...

can be seen as equivalent to:

  $r = &$x;
  $r = $y;

so of course $r will get changed by the second loop. People reading this who had the wrong idea
about references are left with the wrong idea.

But function arguments are quite roundabout. First, there was call-time passing by reference so if
you had

  $a = [1, 2, 3];
  $a[1] = &$r;
  call_user_func_array('f', $a);

then f($a, $b, $c) would take $b by reference despite its declaration. Now this code doesn't
work unless you declare f($a, &$b). Thus, it appears very much like "references to
variables" because if normal value and reference value are the same thing (just one has
refcount of 1) then why I can't pass a value by reference? Why it has to be declared by the
function and why it transparently "unreferences" it, copying the value to another value if
it isn't declared?

This automatic unreferencing combined with the fact arrays are CoW in PHP (unlike objects, which is
a well explained fact) might make somebody think that PHP creates complete copy of the array
including any referenced members on any level. After all, it's strange to see PHP making effort
to avoid accidental reference passing (call-time references) to scalars and arrays and yet allowing
to pass a reference inside an array argument that isn't itself a reference.

I believe all this warrants at least a note in the documentation.

> Have you seen the manual's section on references?

Certainly but it's quite terse on a subject that is probably most confusing in PHP. The Passing
by Reference section has 4 paragraphs which is less than even the foreach section.


Previous Comments:
------------------------------------------------------------------------
[2021-03-04 09:26:33] requinix@php.net

> This can happen without foreach:

Correct.

The common misconception about references is that references are "to" variables, and if
you have $x=&$y that means $x refers "to" $y. But that's not true.

The short version: a PHP variable doesn't contain a value itself, but rather the value is a
separate thing and the variable knows which value it should use. $y=1 means there is a value int(1)
and that the "y" variable knows about it. ("y" is like a C pointer.) When you
create a reference with code like $x=&$y, what happens is that a new variable "x" is
created knowing the same underlying value that "y" had, while a regular assignment would
create the variable and also a whole new underlying value for it*.

So a "reference" is what PHP calls a variable whose value is being used in other places
too.

> Perhaps this case should be mentioned not only in foreach's documentation...

Have you seen the manual's section on references?
https://www.php.net/manual/en/language.references.php


(* actually it doesn't because of copy-on-write, but that's not important here)

------------------------------------------------------------------------
[2021-03-04 08:49:42] tshumbeo at mailhouse dot biz

> And when $a is copied into the f() function, its $a[2] is a copy *of the reference*.

I understand now. Thanks. This can happen without foreach:

<?php
$a = [1, 2, 3];
$a[1] = &$x;
// $a[1] is &NULL
f($a);
// $a[1] is now &array(NULL)
?>

Perhaps this case should be mentioned not only in foreach's documentation (where it's not
obvious at all) but in one on parameter passing or references? This doesn't have anything to do
with foreach after all.

------------------------------------------------------------------------
[2021-03-04 08:28:42] requinix@php.net

> In other words, there is an implicit $reference = <next array member>;
> assignment in loop 2.

No. You missed an important part in the note's first sentence:

> Reference of a $value and the last array element remain even after the foreach
> loop.

*The last element is also a reference*.

In your code, that means $reference *and* $a[2] are references. And when $a is copied into the f()
function, its $a[2] is a copy *of the reference*.

------------------------------------------------------------------------
[2021-03-04 07:36:41] tshumbeo at mailhouse dot biz

Please read the test script carefully. The documentation says about this case:

  foreach ($array as &$reference) { ... }
  // followed by:
  foreach ($array as  $reference) { ... }

In other words, there is an implicit $reference = <next array member>; assignment in loop 2.
However, in my case there is no such assignment: the array is passed into the function via parameter
that IS NOT a reference:

  foreach ($array as &$reference) { ... }
  f($array);

Moreover, inside f() there are no references either and yet the outer $reference somehow affects how
f() assigns array members (which must be a copy!).

How can you explain this?

------------------------------------------------------------------------
[2021-03-03 21:00:00] requinix@php.net

There is a big red note in foreach's documentation about this.

------------------------------------------------------------------------


The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at

    https://bugs.php.net/bug.php?id=80826


--
Edit this bug report at https://bugs.php.net/bug.php?id=80826&edit=1


Thread (7 messages)

« previous php.bugs (#232533) next »