470,810 Members | 912 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 470,810 developers. It's quick & easy.

PHP brain teaser

Here's a little brain teaser distilled from a bug that took me a rather
long time to figure out. The two functions in the example below behave
differently. The difference is easy to spot, of ocurse. The challenge
is correctly explaining why this is so. Why does the second function
seemingly corrupt the cloned copy of an object?

Sample code:

<?php

// uncomment the clone operator for PHP 5

function Bobcat(&$obj) {
$clone = /* clone */ $obj;
$obj->attributes['Length'] = 0;
$obj->data = "";
return $clone;
}

function BritneySpear(&$obj) {
$attr =& $obj->attributes;
$clone = /* clone */ $obj;
$obj->attributes['Length'] = 0;
$obj->data = "";
return $clone;
}

$data = "This is a test";

$obj1->attributes = array('Length' => strlen($data));
$obj1->data = $data;
$clone1 = Bobcat($obj1);
print_r($clone1);

$obj2->attributes = array('Length' => strlen($data));
$obj2->data = $data;
$clone2 = BritneySpear($obj2);
print_r($clone2);

?>

Result:

stdClass Object
(
[attributes] => Array
(
[Length] => 14
)

[data] => This is a test
)
stdClass Object
(
[attributes] => Array
(
[Length] => 0
)

[data] => This is a test
)

Jan 13 '06 #1
15 2362
On 2006-01-13, Chung Leong <ch***********@hotmail.com> wrote:
Here's a little brain teaser distilled from a bug that took me a rather
long time to figure out. The two functions in the example below behave
differently. The difference is easy to spot, of ocurse. The challenge
is correctly explaining why this is so. Why does the second function
seemingly corrupt the cloned copy of an object?


My guess is the following:
When php initializes a new object (copy/clone) it will notice that in the second
method the reference count to $obj->attributes is 2. Therefore it will assign
new memory for the copied/cloned instance. This explains why we see 0 instead of
14.

--
Met vriendelijke groeten,
Tim Van Wassenhove <http://timvw.madoka.be>
Jan 13 '06 #2
On 2006-01-13, Tim Van Wassenhove <ti***@users.sourceforge.net> wrote:
On 2006-01-13, Chung Leong <ch***********@hotmail.com> wrote:
Here's a little brain teaser distilled from a bug that took me a rather
long time to figure out. The two functions in the example below behave
differently. The difference is easy to spot, of ocurse. The challenge
is correctly explaining why this is so. Why does the second function
seemingly corrupt the cloned copy of an object?


My guess is the following:
When php initializes a new object (copy/clone) it will notice that in the second
method the reference count to $obj->attributes is 2. Therefore it will assign
new memory for the copied/cloned instance. This explains why we see 0 instead of
14.


Actually, the new memory is not assigned when the constructor is called, but
when the $obj->attributes['Length'] is set to 0 in the second method (the
copy-on-write behaviour of php).

--
Met vriendelijke groeten,
Tim Van Wassenhove <http://timvw.madoka.be>
Jan 13 '06 #3
On 2006-01-13, Tim Van Wassenhove <ti***@users.sourceforge.net> wrote:
On 2006-01-13, Tim Van Wassenhove <ti***@users.sourceforge.net> wrote:
On 2006-01-13, Chung Leong <ch***********@hotmail.com> wrote:
Here's a little brain teaser distilled from a bug that took me a rather
long time to figure out. The two functions in the example below behave
differently. The difference is easy to spot, of ocurse. The challenge
is correctly explaining why this is so. Why does the second function
seemingly corrupt the cloned copy of an object?


My guess is the following:
When php initializes a new object (copy/clone) it will notice that in the second
method the reference count to $obj->attributes is 2. Therefore it will assign
new memory for the copied/cloned instance. This explains why we see 0 instead of
14.


Actually, the new memory is not assigned when the constructor is called, but
when the $obj->attributes['Length'] is set to 0 in the second method (the
copy-on-write behaviour of php).


My brain is tired now as i'm not able to come up with the right words to explane
what is happening.But the reason for this behaviour is explained in
http://derickrethans.nl/files/phparc...es-article.pdf (see figure 6)
--
Met vriendelijke groeten,
Tim Van Wassenhove <http://timvw.madoka.be>
Jan 13 '06 #4
"Chung Leong" <ch***********@hotmail.com> wrote in message
news:11**********************@g43g2000cwa.googlegr oups.com...
Here's a little brain teaser distilled from a bug that took me a rather
long time to figure out. The two functions in the example below behave
differently. The difference is easy to spot, of ocurse. The challenge
is correctly explaining why this is so. Why does the second function
seemingly corrupt the cloned copy of an object?
.... $attr =& $obj->attributes;


Since this is the only difference in the two functions, the bug must be
here.

Let me tell you what I'm guessing, but I'm not sure if this is the case
really. $attr is inside the BritneySpear variable scope, and since it's
assigned reference to $obj->attributes which is copied to $clone, the
$clone->attributes is destroyed as the function exits, since $attr is
destroyed.

--
"En ole paha ihminen, mutta omenat ovat elinkeinoni." -Perttu Sirviö
sp**@outolempi.net | Gedoon-S @ IRCnet | rot13(xv***@bhgbyrzcv.arg)
Jan 13 '06 #5

Tim Van Wassenhove wrote:
On 2006-01-13, Tim Van Wassenhove <ti***@users.sourceforge.net> wrote:
On 2006-01-13, Chung Leong <ch***********@hotmail.com> wrote:
Here's a little brain teaser distilled from a bug that took me a rather
long time to figure out. The two functions in the example below behave
differently. The difference is easy to spot, of ocurse. The challenge
is correctly explaining why this is so. Why does the second function
seemingly corrupt the cloned copy of an object?


My guess is the following:
When php initializes a new object (copy/clone) it will notice that in the second
method the reference count to $obj->attributes is 2. Therefore it will assign
new memory for the copied/cloned instance. This explains why we see 0 instead of
14.


Actually, the new memory is not assigned when the constructor is called, but
when the $obj->attributes['Length'] is set to 0 in the second method (the
copy-on-write behaviour of php).

--
Met vriendelijke groeten,
Tim Van Wassenhove <http://timvw.madoka.be>


This is the case for the first function. In the second function, the
change to the original object writes through to the cloned object. So
somehow copy-on-write isn't being triggered...

Jan 13 '06 #6
On Thu, 12 Jan 2006 19:01:10 -0800, Chung Leong wrote:
Here's a little brain teaser distilled from a bug that took me a rather
long time to figure out. The two functions in the example below behave
differently. The difference is easy to spot, of ocurse. The challenge
is correctly explaining why this is so. Why does the second function
seemingly corrupt the cloned copy of an object?


It doesn't. You are not writing to the clone, you are writing to the
original. You are setting the original data to "", not the clone. Clone
is a bitwise copy, not a reference. If you want it to be something else
then a bitwise copy, you have to define the __clone function. What you
should have written in place of "clone" is $clone =& $obj; Then the
assignment would work and the function would return a reference to
the original object, which is also passed by reference.
--
http://www.mgogala.com

Jan 13 '06 #7
On Fri, 13 Jan 2006 15:12:29 +0000, Mladen Gogala wrote:
On Thu, 12 Jan 2006 19:01:10 -0800, Chung Leong wrote:
Here's a little brain teaser distilled from a bug that took me a rather
long time to figure out. The two functions in the example below behave
differently. The difference is easy to spot, of ocurse. The challenge
is correctly explaining why this is so. Why does the second function
seemingly corrupt the cloned copy of an object?


It doesn't. You are not writing to the clone, you are writing to the
original. You are setting the original data to "", not the clone. Clone
is a bitwise copy, not a reference. If you want it to be something else
then a bitwise copy, you have to define the __clone function. What you
should have written in place of "clone" is $clone =& $obj; Then the
assignment would work and the function would return a reference to
the original object, which is also passed by reference.

This produces the results you probably expected:

<?php

function Bobcat(&$obj) {
$clone = clone $obj;
$obj->attributes['Length'] = 0;
$obj->data = "";
return $clone;
}

function BritneySpear(&$obj) {
$attr =& $obj->attributes;
$clone =& $obj;
$obj->attributes['Length'] = 0;
$obj->data = "";
return $clone;
}

$data = "This is a test";

$obj1->attributes = array('Length' => strlen($data));
$obj1->data = $data;
$clone1 = Bobcat($obj1);
print_r($clone1);

$obj2->attributes = array('Length' => strlen($data));
$obj2->data = $data;
$clone2 = BritneySpear($obj2);
print_r($clone2);

?>

$ php ttt
stdClass Object
(
[attributes] => Array
(
[Length] => 14
)

[data] => This is a test
)
stdClass Object
(
[attributes] => Array
(
[Length] => 0
)

[data] =>
)
$

--
http://www.mgogala.com

Jan 13 '06 #8
On 2006-01-13, Mladen Gogala <go****@sbcglobal.net> wrote:
On Fri, 13 Jan 2006 15:12:29 +0000, Mladen Gogala wrote:
On Thu, 12 Jan 2006 19:01:10 -0800, Chung Leong wrote:
Here's a little brain teaser distilled from a bug that took me a rather
long time to figure out. The two functions in the example below behave
differently. The difference is easy to spot, of ocurse. The challenge
is correctly explaining why this is so. Why does the second function
seemingly corrupt the cloned copy of an object?
It doesn't. You are not writing to the clone, you are writing to the
original. You are setting the original data to "", not the clone. Clone
is a bitwise copy, not a reference. If you want it to be something else
then a bitwise copy, you have to define the __clone function. What you
should have written in place of "clone" is $clone =& $obj; Then the
assignment would work and the function would return a reference to
the original object, which is also passed by reference.


Changing the statement below to: $attr = $obj->attributes would already make
the curiosity disappear.
$attr =& $obj->attributes;


I still think it's because after this statement the container that holds the
$obj->attributes will now have is_ref = 1 which seems to lead to a copy of this
container when $obj is assigned to $clone ($clone = $obj). For some reason php
doesn't seem to make a deep-copy of the array but simply makes an array with
references to the original array elements.

--
Met vriendelijke groeten,
Tim Van Wassenhove <http://timvw.madoka.be>
Jan 13 '06 #9
Tim Van Wassenhove wrote:

I still think it's because after this statement the container that holds the
$obj->attributes will now have is_ref = 1 which seems to lead to a copy of this
container when $obj is assigned to $clone ($clone = $obj). For some reason php
doesn't seem to make a deep-copy of the array but simply makes an array with
references to the original array elements.


That's it. You have got to the root of the problem. During cloning, PHP
does not create a copy of an object property if it is a reference. From
the manual: "Any properties that are references to other variables,
will remain references." The hard part was, of course, recognizing that
$obj->attributes is a reference. It's extremely unintuitive that the
line

$attr =& $obj->attributes;

not only makes $attr a reference, but turns $obj->attributes into a
reference too. Programmers usually don't expect side-effects on the
right side of an assignment operator.

Jan 14 '06 #10

"Chung Leong" <ch***********@hotmail.com> wrote in message
news:11**********************@g44g2000cwa.googlegr oups.com...
Tim Van Wassenhove wrote:

I still think it's because after this statement the container that holds
the
$obj->attributes will now have is_ref = 1 which seems to lead to a copy
of this
container when $obj is assigned to $clone ($clone = $obj). For some
reason php
doesn't seem to make a deep-copy of the array but simply makes an array
with
references to the original array elements.


That's it. You have got to the root of the problem. During cloning, PHP
does not create a copy of an object property if it is a reference. From
the manual: "Any properties that are references to other variables,
will remain references." The hard part was, of course, recognizing that
$obj->attributes is a reference. It's extremely unintuitive that the
line

$attr =& $obj->attributes;

not only makes $attr a reference, but turns $obj->attributes into a
reference too. Programmers usually don't expect side-effects on the
right side of an assignment operator.


This doesn't sound technically correct. an operator (such as =& ) in a
parser is generally never allowed to modify the RHS (right-hand-side). This
jives with the article he mentioned.
The correct way to read $b =& $a is $b is assigned a reference to $a.
according to the diagrams shown, $a and $b are both C pointers (to data) in
the first place.
in C (under the hood), the pointer assignment is simply b=a.
PHP's = is probably a simple C call to malloc() the LHS pointer to allocate
some memory and then memcpy() to copy the RHS data over that newly allocated
memory.
Ever wonder why a plain var like $a can be assigned NULL? because possibly
a C pointer can be assigned NULL. You learn this stuff in ANSI C classes
(which is what PHP was likely written in).
This tells me that $a=5; probably means $a is a pointer to an int. PHP's ->
is a dereferencing an extra pointer.
You can think of then as references before they were ever assigned.
references are simply pointers. variable names are simply pointers.
Technically, the reference (pointer) itself should get copied in the process
of cloning if I am correct. I guess I would have to look at that weird code
again. did you find a bug in PHP there?
That article mentions at the end that it's possible to corrupt memory with
references (that's definitely true with C pointers... been there, done
that - ever heard of GPF?).
Jan 15 '06 #11
Jim Michaels wrote:
This doesn't sound technically correct. an operator (such as =& ) in a
parser is generally never allowed to modify the RHS (right-hand-side). This
jives with the article he mentioned.


In a way the term "reference" is unfortunate. In C++ being a reference
is a variable's intrinsic, whereas in PHP a variable "is a reference"
if the data it points to is shared--an external condition.

One could argue that there is no changes on the right-hand-side of an
assignment if = and & are treated as separate operators and any
side-effect is caused by the latter. You can put white spaces between
the two after all. On the other hand, & is really just a modifier and
we have just one operator even with white spaces. In any event, a
couple changes can occur on the right-hand-side of a =&:

1. $b =& $a makes both $b and $a references. This is actually perfectly
logical: since $a and $b are point to the same data, $a can be used to
modify what's in $b--hence it is a reference. It's only funky from the
C/C++ perspective, as the type of $a has suddenly changed.

2. Autovivication is triggered.

Example:

<?php
error_reporting(E_ALL);
$b = &$a['Goodbye']['cruel']['world']->letter;
print_r($a);
?>

Result:

Array
(
[Goodbye] => Array
(
[cruel] => Array
(
[world] => stdClass Object
(
[letter] =>
)

)

)

)

Jan 15 '06 #12
I would agree that & is a modifier. if $a were truly converted from real
data into a reference the data would have been destroyed by modifying the
RHS. That's illegal in a language parser. I was speaking more about the
internal representation and the way the parser probably works. & just says
"give me the address of $a". Maybe I am misunderstanding what you are trying
to say.
"Chung Leong" <ch***********@hotmail.com> wrote in message
news:11**********************@g14g2000cwa.googlegr oups.com...
Jim Michaels wrote:
This doesn't sound technically correct. an operator (such as =& ) in a
parser is generally never allowed to modify the RHS (right-hand-side).
This
jives with the article he mentioned.


In a way the term "reference" is unfortunate. In C++ being a reference
is a variable's intrinsic, whereas in PHP a variable "is a reference"
if the data it points to is shared--an external condition.

One could argue that there is no changes on the right-hand-side of an
assignment if = and & are treated as separate operators and any
side-effect is caused by the latter. You can put white spaces between
the two after all. On the other hand, & is really just a modifier and
we have just one operator even with white spaces. In any event, a
couple changes can occur on the right-hand-side of a =&:

1. $b =& $a makes both $b and $a references. This is actually perfectly
logical: since $a and $b are point to the same data, $a can be used to
modify what's in $b--hence it is a reference. It's only funky from the
C/C++ perspective, as the type of $a has suddenly changed.

2. Autovivication is triggered.

Example:

<?php
error_reporting(E_ALL);
$b = &$a['Goodbye']['cruel']['world']->letter;
print_r($a);
?>

Result:

Array
(
[Goodbye] => Array
(
[cruel] => Array
(
[world] => stdClass Object
(
[letter] =>
)

)

)

)

Jan 15 '06 #13
I don't know what you're driving at. I'm telling you that's how PHP
works. Your speculations are simply incorrect.

Jan 16 '06 #14
Chung Leong wrote:

In a way the term "reference" is unfortunate. In C++ being a reference
is a variable's intrinsic, whereas in PHP a variable "is a reference"
if the data it points to is shared--an external condition.

Actually, not. In C++ a reference is a different type of variable.

int i = 4; // the real thing
int & j = i; // a reference to i.

The second statement does NOT change i in any way. the same is true in PHP:

$i = 4;
$j = & $i;

$i is an value; $j is a reference to the value.

The big difference is - $i has independent storage assigned to it to
contain the data. $j does not contain any data - it only "refers" to $i.
One could argue that there is no changes on the right-hand-side of an
assignment if = and & are treated as separate operators and any
side-effect is caused by the latter. You can put white spaces between
the two after all. On the other hand, & is really just a modifier and
we have just one operator even with white spaces. In any event, a
couple changes can occur on the right-hand-side of a =&:

No, there is no change in $i when you assign a reference to it.
1. $b =& $a makes both $b and $a references. This is actually perfectly
logical: since $a and $b are point to the same data, $a can be used to
modify what's in $b--hence it is a reference. It's only funky from the
C/C++ perspective, as the type of $a has suddenly changed.


Nope, the type of $a has NOT changed.

--
==================
Remove the "x" from my email address
Jerry Stuckle
JDS Computer Training Corp.
js*******@attglobal.net
==================
Jan 16 '06 #15
Jerry Stuckle wrote:
Actually, not. In C++ a reference is a different type of variable.

int i = 4; // the real thing
int & j = i; // a reference to i.

The second statement does NOT change i in any way. the same is true in PHP:

$i = 4;
$j = & $i;

$i is an value; $j is a reference to the value.
That's a common misconception about references in PHP. I suggest
reading the article that Tim referred to earlier.
No, there is no change in $i when you assign a reference to it.


I am afraid you're arguing with reality here. To illustrate...

<?php

$obj->i = 5;
debug_zval_dump($obj);

$obj->j =& $obj->i;
debug_zval_dump($obj);

?>

Result:

object(stdClass)(1) refcount(2){
["i"]=>
long(5) refcount(1)
}
object(stdClass)(2) refcount(2){
["i"]=>
&long(5) refcount(2)
["j"]=>
&long(5) refcount(2)
}

Jan 16 '06 #16

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

7 posts views Thread by One Handed Man \( OHM - Terry Burns \) | last post: by
7 posts views Thread by Mark A | last post: by
2 posts views Thread by oorga.power | last post: by
reply views Thread by mihailmihai484 | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.