I have this php code
$a = array('one');
$b[0] = &$a[0];
$c = $a;
$b[0] = 'three';
$a = array('two');
var_dump($a); echo '<br/>';
var_dump($b); echo '<br/>';
var_dump($c); echo '<br/>';
Which outputs this ->
array(1) { [0]=> string(3) "two" }
array(1) { [0]=> &string(5) "three" }
array(1) { [0]=> &string(5) "three" }
$b and $a points to the same value but $c should not point to the same value, it should have its own copy of the value. Then when I change the value of $b to three I don't understand why the value of $c also changes. How can I prevent this? Also, when I change the value of $a to 'two', why doesn't $b also changes to 'two'?
Thanks in advance.
Your problem is:
$b[0] = &$a[0];
This references the first element of the array. As evident by the var_dump() output &string(5).
So $b does not reference the entire array, only the first element.
To reference the entire array, you need to do:
$b = &$a;
Doing so works as you expect:
// $a
array(1) {
[0]=>
string(3) "two"
}
// $b
array(1) {
[0]=>
string(3) "two"
}
// $c
array(1) {
[0]=>
string(3) "one"
}
Read more about Arrays in PHP.
This is a step-by-step explanation:
$a = array('one');
$b[0] = &$a[0];
$b is now an array and its first element references the first element of $a. In the symbol table, $b[0] and $a[0] point to the same underlying zval.
$c = $a;
$c references $a now, but a copy has not been made (copy on write semantics). It looks a lot like this:
$c ----/----> $a $b
(0: 'one') 0: 'one' <---- 0: 'one'
The next statement:
$b[0] = 'three';
This updates $a[0] as well, and in turn updates $c[0], because $a itself is not changed. It now looks like;
$c ----/----> $a $b
(0: 'three') 0: 'three' <---- 0: 'three'
Next statement:
$a = array('two');
Disconnect $a from $c and $a[0] from $b[0].
$c $a $b
0: 'three' 0: 'two' 0: 'three'
Prevention
To prevent this behaviour, you need to reference the whole array instead of just one element:
$b = &$a;
By doing this, $b and $a now reference the same zval (the array itself), so any changes made will disconnect it from $c.
I would consider these edge cases of the language though and I would advice not to use references if you don't need to.
It's important to understand what you're doing when assigning by reference vs value. Let's go line by line.
$a = array('one');
Memory location created, we'll call it M1. $a points to M1 and within M1 we have an array with 1 entry, let's call it M1-A1.
$b[0] = &$a[0];
Now what we're doing is pointing $b[0] to M1-A1. Remember that $a[0] also points to M1-A1, so both are pointing to this particular part of memory. Remember that $b itself has it's own memory location, M2, but within M2 we point to M1-A1 (i.e. M2-A1 is pointing to M1-A1).
$c = $a;
Since we're not assigning by reference, what we get is a new memory location, let's call it M3, but within M3 there is an array with the first entry still pointing to M1-A1.
So now we have M1, M2, M3 with an array entry in M2 and M3 pointing to M1-A1.
$b[0] = 'three';
Since $b[0] actually points to M1-A1, we're actually changing the value of M1-A1 memory spot. So anything that references M1-A1's spot will also see this value change.
$a = array('two');
We're completely changing the memory location for $a at this point. Originally it was M1, now we're creating a new memory location, M4. Nothing else points to M4 and M4-A1 DOES NOT point to M1-A1.
So when we do the var dump we get the values you mentioned.
I probably made this more confusing but try drawing it on paper and it'll be pretty clear. Understand that everything is stored in memory and variables just point to memory locations. Once you understand that principle, it'll all fall in place.
They all refer to the same array object in memory
Related
I have following code
public function index() {
$a = 2;
$b = 8;
$arr[] = $a;
$arr[] = &$a;
$arr[] = $a;
$this->dmp($arr);
$arr[1] = $b;
$this->dmp($arr);
$a++;
$b++;
$this->dmp($arr);
}
private function dmp($val) {
echo '<pre>';
var_dump($val);
echo '</pre>';
}
which gives me following result
array(3) {
[0]=>
int(2)
[1]=>
&int(2)
[2]=>
int(2)
}
array(3) {
[0]=>
int(2)
[1]=>
&int(8)
[2]=>
int(2)
}
array(3) {
[0]=>
int(2)
[1]=>
&int(9)
[2]=>
int(2)
}
Q: Why the value on index 1 is not replaced with value of $b variable, but there is still the reference? The reference remains even after calling $arr[1] = $b; without the appersand?
Here is a simple explanation:
$arr[0] = $a; // Index 0 holds the same value as $a which is 2
$arr[1] = &$a; // Index 1 holds a reference to variable $a
$arr[2] = $a; // Index 2 holds the same value as $a which is 2
You then instruct the code to change the value of the variable referenced by index 1, to hold the same value as $b which is 8.
$arr[1] = $b; // Index 1 references variable $a so $a will become 8
If you do var_dump($a) now, you will see that $a is 8.
You then increment both $a and $b, which at this point have the same value. They both will now have the value 9
The contents of the array at the end are the same as initially specified. The only difference is that the variable which is referenced by element with index 1 has now a different value.
$arr[0] = 2; // Index 0 holds the value 2
$arr[1] = &$a; // Index 1 holds a reference to variable $a which has a value 9 now
$arr[2] = 2; // Index 2 holds the value 2
When you assign by reference it creates a link between the two, they are pointing to the same result. So when you make another assignment it also assigns to the referenced variable. If you inspect $a you will see it changes to 8 and then 9. To break the reference use unset().
Demo
This $arr[] = &$a; says make $arr[1] point to the same data as $a.
This $arr[1] = $b; says assign the value of $b to $arr[1], which is 8, and $a is linked to $arr[1] so $a is 8 as well.
Check What References Do
Whats the meaning of $arr[0] result "2" in the code given below, $arr2 is copying $arr and increasing its first value by one, so the result of $arr2[0] "2" is understaning,but whats happening with $arr, when i pass by reference $arr[0] to $a like so $a=&$arr[0] the result of $arr[0] is 2, when i pass it by value $a=$arr[0[ the result of $arr[0] would be set to 1 as it should, can anyone enlighten me on this?
<?php
$a = 1;
$arr = array(1);
$a = &$arr[0];
$arr2 = $arr;
$arr2[0]++;
echo $arr[0]. "<br>";//2
echo $arr2[0]. "<br>";//2
?>
References in PHP are not like pointers; when you assign or pass something by reference, you create what we might call a "reference set" where both variables are references to the same "zval" (the structure in memory which holds the type and value of a variable).
An important consequence of this is that references are symmetrical. I tend to write assign by reference as $foo =& $bar rather than $foo = &$bar to emphasise this: the operator doesn't just take a reference to $bar and put it into $foo, it affects both operands.
With that in mind, let's go through your example:
$arr = array(1);
This will create two zvals: one for the array $arr itself, and one for the value in position 0 of that array ($arr[0]).
$a =& $arr[0];
This binds $a and $arr[0] into a reference set. Whichever of those names we use, we will be using the same zval that was previously created, and currently holds 1.
$arr2 = $arr;
This copies the contents of $arr into a new array zval, $arr2. But it doesn't resolve the references inside that array into values, it just copies the fact that they are a reference.
So $arr2[0] is now also part of the reference set containing $arr[0] and $a.
$arr2[0]++;
This increments the zval pointed to by our reference set, from 1 to 2.
echo $arr[0]. "<br>";//2
echo $arr2[0]. "<br>";//2
Since these are both part of the same reference set, they both point to the same zval, which is integer 2.
The inevitable question is, "bug or feature?" A more useful example would be passing an array containing references into a function:
function foo($array) {
$array[0] = 42;
$array[1] = 69;
}
$a = 1;
$b = 1;
$foo = [ &$a, &$b ];
foo($foo);
echo $a; // 42
echo $b; // 69
Like assignment, passing into the function didn't break the references, so code manipulating them can safely be re-factored into multiple functions.
Not sure if this is a bug but var_dump() can help to explain.
<?php
$a = 1;
$arr = array(1);
var_dump( $arr );
$a = &$arr[0];
var_dump( $arr );
$arr2 = $arr;
$arr2[0]++;
Output:
array(1) {
[0]=>
int(1)
}
array(1) {
[0]=>
&int(1)
}
Take note of &int(1) in the second var_dump(). This tells us that position #0 of $arr has been turned into a reference pointer to a position in PHP's memory instead of remaining a dedicated value.
So when you perform $arr2 = $arr;, $arr2 receives that reference pointer as well.
I found this awfully old comment in the PHP docs comments, but can't get my mind around it why it outputs "hihaha" and not "eita" in the first example. $a is changed and I'd assume that "hihaha" is removed for good. If not, then why is it so that if the change is to null or assigning a copy of another variable, then the "hihaha" IS removed for good?
// 1. example
$a = "hihaha";
$b = &$a;
$c = "eita";
$a = &$c; // why doesn't this purge "hihaha" from existence?
echo $b; // shows "hihaha" WHY?
// 2. example
$a = "hihaha";
$b = &$a;
$a = null;
echo $b; // shows nothing (both are set to null)
// 3. example
$a = "hihaha";
$b = &$a;
$c = "eita";
$a = $c;
echo $b; // shows "eita"
Is this a "good way" towards the circular references problem?
Starting with $a = "hihaha";, when you do $b = &$a;, $b is not referencing $a. It is referencing the content of $a. As it says in PHP: What References Do:
$a and $b are completely equal here. $a is not pointing to $b or vice versa. $a and $b are pointing to the same place.
Then after $c = "eita";, when you do $a = &$c;, $a is now referencing the content of $c ("eita").
This does not affect $b at all. $b is still referencing the original content of $a ("hihaha"). Pointing $a at something else does not change that.
In case you have a more mspaint learning style, here is a visual aid representing the first four statements of example 1:
In the second example, $a and $b are still pointing at the same content when $a is set to null, so $b is now referencing null as well. Visually:
Think of a variable as pointing to a reference - to break down Example 1...
1
$a = "hihaha";
$a points to the reference for the string hihaha, lets call it R1
2
$b =& $a;
Here we are saying, point $b to the same reference as$a (R1)
3
$c = "eita";
$c points to the reference for the string eita, lets call it R2
4
$a =& $c;
Now we say, point $a to the same reference as $c ($b still points to R1)
At this stage,
$a and $c point to R2,
$b points to R1
- should be easy to guess what happens next!
5
echo $b; // hihaha
We now know that echoing $b will output R1!
Hope that helps!
Have a read of http://php.net/manual/en/language.references.whatdo.php
I'm reading about php and it says,
== is Equality such that $a == $b is true if $a and $b have the same elements.
=== is Identity such that $a === $b is true if $a and $b have the same elements, with the same types, in the same order.
So, I thought I'd try and see the difference myself and wrote with this little script:
$a = array(1, 2, 3);
$b = array(2, 3, 1);
if ($a==$b) {echo "yeehaw!";} else {echo "nope";}
if ($a===$b) {echo "yup";} else {echo "nope";}
My thought was that the same order wasn't required for two arrays to be equal. However, when I ran this, I got "nope" and "nope".
What is the difference?
The arrays you've provided have the same set of values, but different key-value-pairs.
Try the following use case instead (same key-value-pairs in different order):
$a = array(0=>1, 1=>2, 2=>3);
$b = array(1=>2, 2=>3, 0=>1);
... and the following use case (different data types):
$a = array(1, 2, 3);
$b = array('1', '2', '3');
The documentation[PHP.net] says:
== TRUE if $a and $b have the same key/value pairs.
=== TRUE if $a and $b have the same key/value pairs in the same order and of the same types.
Since your two arrays are not in the same order1, they don't have the same key-value pairs.
var_dump($a);
array(3) {
[0]=> int(1)
[1]=> int(2)
[2]=> int(3)
}
var_dump($b);
array(3) {
[0]=> int(2)
[1]=> int(3)
[2]=> int(1)
}
1 With regards to their construction via array(), which will index the arguments starting with 0.
My thought was that the same order wasn't required for two arrays to be equal.
To make it clear what the documentation meant by same key/value pairs, let's take a look at the actual array contents:
$a = array(
0 => 1,
1 => 2,
2 => 3,
);
$b = array(
0 => 2,
1 => 3,
2 => 1,
);
Clearly, the pairs are different.
So what about that "same order"?
To illustrate that, let's create $b a little different:
$b => array(
2 => 3,
1 => 2,
0 => 1,
);
The == equality will be satisfied because the pairs are now the same. However, because arrays in PHP are ordered maps, a difference in pair order causes the === equality to fail.
Two arrays are considered identical === if:
number of elements is the same
all data types are the same
all elements are in the same order
each array has the same key-value pairs
What is the difference?
The difference between two arrays can mean different things, so this question is normally best answered by using the kind of difference function for arrays that match your expectations.
In your case, equality is (probably) satisfied by the array_diff() function:
!array_diff($a, $b) && !array_diff($b, $a);
If you say no, that's not what I'm looking for, please see the duplicate question "PHP - Check if two arrays are equal" I also left an extended answer there that shows the other possible differences and how to test for those as you're concerned about comparing values and not element (which are key/value pairs).
1st one fails because the elements are different. 2nd one fails because elements are different although type is same. (both should same)
Today I was reading article What Reference Do from PHP's official manual page and found the following piece of code:
<?php
$arr = array(1);
$a =& $arr[0]; //$a and $arr[0] are in the same reference set
$arr2 = $arr; //not an assignment-by-reference!
$arr2[0]++;
/* $a == 2, $arr == array(2) */
And that what manual says about this code:
References inside arrays are potentially dangerous. Doing a normal
(not by reference) assignment with a reference on the right side does
not turn the left side into a reference, but references inside arrays
are preserved in these normal assignments. This also applies to
function calls where the array is passed by value.
Can someone explain me why after execution of the code we will have $a and $arr equal to 2 ?
Perhaps this will make it clearer:
$arr = array(1,1);
$a =& $arr[0]; //$a and $arr[0] are in the same reference set
$arr2 = $arr; //not an assignment-by-reference!
$arr2[0]++;
$arr2[1]++;
/* $a == 2, $arr == array(2) */
var_dump($arr);
//outputs array(2) { [0]=> &int(2) [1]=> int(1) }
The reason the value is incremented is because the reference inside the array is preserved in the normal assignment. $arr2[0], $arr[0] and $a now all refer to the same value, even though $arr2 is a copy of $arr. Note how $arr2[1]++ does not increment $arr[1].
Do a var_dump($arr); and you'll see the issue. $arr[0] will have an &int type.
This means $arr[0] becomes a reference to a value.
And the $arr array will actually have a reference as its first value.
When you duplicate the array, the reference is carried over as the first element will keep being a reference and will still modify the referenced value.
It's an array thing. Seems really weird but once you understand it, it makes sense... as much as PHP makes sense :)
PS: This behavior is why call_user_func_array() can take an array() of references as it's argument and allows you to call functions that accept reference arguments.