Are PHP zvals mutable? - php

I've been reading about memory management in PHP and learned that variables in PHP copy the reference to zvals as long as you don't do a write operation (copy on write paradigm).
https://www.phpinternalsbook.com/php5/zvals/memory_management.html#reference-counting-and-copy-on-write
However, it does not describe what happens when you reassign a value to an already copied zval.
Here's what the book says:
$a = 1; // $a = zval_1(value=1, refcount=1)
$b = $a; // $a = $b = zval_1(value=1, refcount=2)
$c = $b; // $a = $b = $c = zval_1(value=1, refcount=3)
$a++; // $b = $c = zval_1(value=1, refcount=2)
// $a = zval_2(value=2, refcount=1)
Now, if you do another $a++, will it change the value in zval,
$a++; // $a = zval_2(value=3, refcount=1)
or will it create a new zval once again?
$a++; // $a = zval_3(value=3, refcount=1)
Following the logic of the PHP Language Reference, I guess it should be more likely option #1 as long as refcount = 1 (you would not manipulate another variable).

Related

Do static and global modifiers for variables implement a non-modifiable reference?

Source
The PHP doc says
PHP implements the static and global modifier for variables in terms
of references.
<?php
function test_global_ref() {
global $obj;
$new = new stdClass;
$obj = &$new;
}
function test_global_noref() {
global $obj;
$new = new stdClass;
$obj = $new;
}
test_global_ref();
var_dump($obj);
test_global_noref();
var_dump($obj);
?>
Since the program yields NULL as the first output, is this to say that the implemented reference is non-modifiable(hence the reference to &$new is nullified somehow)? The doc says the implementation results in an unexpected behaviour. Is there a logical explanation to this?
This is not about global or static, this is about the concept of reference.
Think about the following codes:
$a = "a"; $b = "b";
$r = &$a;
var_dump($a, $b, $r); # a, b, a
$r = &$b;
var_dump($a, $b, $r); # a, b, b
It's easy to understand, but the important thing is the statement $r = &$b; means copy the reference of $b to $r, so both $b and $r refer to the same value.
Next if you do:
$r = $a;
var_dump($a, $b, $r); # a, a, a
The statement $r = $a; means copy the value of $a to $r, so the value of $r changes from "b" to "a". Since both $b and $r refer to the same value, the value of $b also becomes "a".
Finally if you do:
$r = "r";
var_dump($a, $b, $r); # a, r, r
Still only the value of $b to $r is changed, $a keeps its original value.
Back to your question, your first function is almost equivalent to:
function test_global_ref(&$r) {
$b = "b";
$r = &$b;
}
$a = "a";
test_global_ref($a);
I changed the variable names and values to those corresponding to the above example, hope this is easier to understand. So the global variable $a is passed to the function as a reference $r, when you copy the reference of $b to $r, the global variable $a won't be influenced.

Difference between clone object and normal object

In PHP How it differs when I create cloned object in a variable and new object created using a variable with the same class
For example
$a = new classA();
$b = clone $a;
$c = new classA();
What is the difference between $b and $c ?
You should look at the following example
<?php
class classA {
public $x=0;
}
$a = new classA();
$a->x = 20;
echo $a->x."<br />";
$b = clone $a;
$a->x = 30;
echo $a->x."<br />";
echo $b->x."<br />"; // 20 because x was 20 before cloning $a to $b
$a->x = 50;
echo $a->x."<br />"; // changed to 50
echo $b->x."<br />"; // stil 20, $a
$c = new classA();
echo $c->x;
Using cloning make, you have property x in object $b the same as in object $a because cloning simple copies object. And when creating new object, you will have new object and property value will be 0.
Cloning is simple copying object because by default for objects:
$a = $b;
PHP won't do copying (as for simple types) but will point to exact place in memory.
So for simple types you use:
$a = 5;
$b = $a;
if you want to make a copy, but for objects you need to use clone:
$a = new classA();
$a->x = 20;
$b = clone $a;
to have the same effect.
You should look in manual at Object and references and Cloning to understand those things.

Reference Counting in php - how does it work?

I am trying to understand this article "PHP Manual -> Features -> Garbage Collection"
unfortunately few things are unclear for me.
1.
To avoid having to call the checking of garbage cycles with every
possible decrease of a refcount, the algorithm instead puts all
possible roots (zvals) in the "root buffer".
but what in case
<?php
$a = new \stdClass(); (1)
$a = new \stdClass();
Then I guess the first object become "lost" zval like
no_symbol : (refcount=1, is_ref=1) = stdObject
Will such "lost" zvals be added into root buffer or not? There is no handler for them.
2.
Variables created in function scope, what happened with them?
Ex:
<?php
function test($a = 'abc') {
$c = 123;
return 1;
}
test();
echo 'end';
What happened with $a and $c when gc starts?
These variables still have refcount set to 1.
Will they still be removed? if yes then why and how (what is happening under the cover?)
3.
How can it help for cyclic references?
Ex
<?php
$a = array('abc');
$a[] =& $a;
unset($a);
where
(refcount=1, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='abc',
1 => (refcount=1, is_ref=1)=...
)
1) The original object is replaced with the new one, and thus memory is freed instantly.
echo memory_get_usage().'<br/>';
$a = new stdClass();
echo memory_get_usage().'<br/>';
$a = new stdClass();
echo memory_get_usage().'<br/>';
2) They are gc'ed the second the function completes executing:
echo memory_get_usage().'<br/>';
function test($a = 'abc') {
$c = 123;
return 1;
}
echo memory_get_usage().'<br/>';
test();
echo memory_get_usage().'<br/>';
3) unsetting $a will leave the referenced variable in memory. You need to set the value to NULL first, and then unset.
echo memory_get_usage().'<br/>';
$a = array('abc');
echo memory_get_usage().'<br/>';
$a[] =& $a;
echo memory_get_usage().'<br/>';
$a = null;
unset($a);
echo memory_get_usage().'<br/>';

Detecting whether a PHP variable is a reference / referenced

Is there a way in PHP to determine whether a given variable is a reference to another variable and / or referenced by another variable? I appreciate that it might not be possible to separate detecting "reference to" and "reference from" given the comment on php.net that setting $a=& $b means "$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."
If it's not possible to determine whether a given variable is a reference / referenced, is there a generalised way of determining if two variables are references of each other? Again, a comment on php.net supplies a function for doing such a comparison - although it is one that involves editing one of the variables and seeing if the other variable is similarly effected. I'd rather avoid doing this if possible since some of the variables I'm considering make heavy use of magic getters / setters.
The background to the request in this instance is to write a debugging function to help view structures in detail.
Full working example:
function EqualReferences(&$first, &$second){
if($first !== $second){
return false;
}
$value_of_first = $first;
$first = ($first === true) ? false : true; // modify $first
$is_ref = ($first === $second); // after modifying $first, $second will not be equal to $first, unless $second and $first points to the same variable.
$first = $value_of_first; // unmodify $first
return $is_ref;
}
$a = array('foo');
$b = array('foo');
$c = &$a;
$d = $a;
var_dump(EqualReferences($a, $b)); // false
var_dump(EqualReferences($b, $c)); // false
var_dump(EqualReferences($a, $c)); // true
var_dump(EqualReferences($a, $d)); // false
var_dump($a); // unmodified
var_dump($b); // unmodified
You can use debug_zval_dump:
function countRefs(&$var) {
ob_start();
debug_zval_dump(&$var);
preg_match('~refcount\((\d+)\)~', ob_get_clean(), $matches);
return $matches[1] - 4;
}
$var = 'A';
echo countRefs($var); // 0
$ref =& $var;
echo countRefs($var); // 1
This though will not work anymore as of PHP 5.4 as they removed call time pass by reference support and may throw an E_STRICT level error on lower versions.
If you wonder, where the -4 in the above function come from: You tell me... I got it by trying. In my eyes it should be only 3 (the variable, the variable in my function, the variable passed to zend_debug_zval), but I'm not too good at PHP internals and it seems that it creates yet another reference somewhere on the way ;)
Maybe xdebug_debug_zval() helps you. http://www.xdebug.org/docs/all_functions
Edit:
It seems I've answered the question 'is it possible to check if two variables are referencing same value in memory' not the actual question asked. :P
As far as 'plain' variables go the answer is 'no'.
As far as objects go - maybe.
All objects are by default handled by references. Also each object has it's serial number which you can see when you var_dump() it.
>> class a {};
>> $a = new a();
>> var_dump($a);
object(a)#12 (0) {
}
If you could get somehow to this #, you could effectively compare it for two variables, and see if they point to the same object. The question is how to get this number. var_export() does not return it. I don't see snything in Reflection classes that would get it either.
One thing that comes to my mind is using output buffering + regex
Take a peak at xdebug_debug_zval(). Right now, that's the only way to really know if you can determine everything about the variable's zval.
So here are a couple of helper functions to determine some helpful information:
function isRef($var) {
$info = getZvalRefCountInfo($var);
return (boolean) $info['is_ref'];
}
function getRefCount($var) {
$info = getZvalRefCountInfo($var);
return $info['refcount'];
}
function canCopyOnWrite($var) {
$info = getZvalRefCountInfo($var);
return $info['is_ref'] == 0;
}
function canReferenceWithoutCopy($var) {
$info = getZvalRefCountInfo($var);
return $info['is_ref'] == 1 || $info['refcount'] == 1;
}
function getZvalRefCountInfo($var) {
ob_start();
xdebug_debug_zval($var);
$info = ob_get_clean();
preg_match('(: \(refcount=(\d+), is_ref=(\d+)\))', $info, $match);
return array('refcount' => $match[1], 'is_ref' => $match[2]);
}
So with some sample variables:
$a = 'test';
$b = $a;
$c = $b;
$d =& $c;
$e = 'foo';
We can test if a variable is a reference:
isRef('a'); // false
isRef('c'); // true
isRef('e'); // false
We can get the number of variables linked to the zval (not necessarily a reference, can be for copy-on-write):
getRefCount('a'); // 2
getRefCount('c'); // 2
getRefCount('e'); // 1
We can test if we can copy-on-write (copy without performing a memory copy):
canCopyOnWrite('a'); // true
canCopyOnWrite('c'); // false
canCopyOnWrite('e'); // true
And we can test if we can make a reference without copying the zval:
canReferenceWithoutCopy('a'); // false
canReferenceWithoutCopy('c'); // true
canReferenceWithoutCopy('e'); // true
And now, we can check if a variable references itself through some black magic:
function isReferenceOf(&$a, &$b) {
if (!isRef('a') || getZvalRefCountInfo('a') != getZvalRefCountInfo('b')) {
return false;
}
$tmp = $a;
if (is_object($a) || is_array($a)) {
$a = 'test';
$ret = $b === 'test';
$a = $tmp;
} else {
$a = array();
$ret = $b === array();
$a = $tmp;
}
return $tmp;
}
It's a bit hacky since we can't determine what other symbols reference the same zval (only that other symbols reference). So this basically checks to see if $a is a reference, and if $a and $b both have the same refcount and reference flag set. Then, it changes one to check if the other changes (indicating they are the same reference).

Is it possible to delay variable implement(?)/recognize(?) in PHP?

For example:
A.php (the config file):
<?php
$a = array('name'=>'wine[$index][name]',
'value'=>'wine[$index][value]'
)
?>
B.php:
include_once(a.php)
...
//for simple
$index = 0;
$b = $a;
//actual code like
foreach($data as $index=>$value)
$b += $a
I know this example won't work, just for explaination, i wanna if it is possible (if possible, how?) to delay variable $index to take value when $b = $a ?
make "a" a function
function a($index) { global $data; return $data[$index] ... }
$b = a($index);

Categories