1

I'm trying to understand PHP's zvals. So consider the following code:

<?php
$randomByteString = 'abcd';

$val = 0;
for ($i = 0; $i < 4; ++$i) {
    $val |= ord($randomByteString[$i]) << ($i * 8);
}

echo $val;

It seems to me that a zval would be created for each of these statements:

  • $i = 0
  • ++$i
  • $i * 8
  • $randomByteString[$i]
  • ord($randomByteString[$i])
  • and on any change to $val

Is that correct?

Andrea
  • 19,134
  • 4
  • 43
  • 65
  • No, why should a new zval be created for `++$i` when there's already a zval for `$i`? All you're doing is modifying the value stored in that zval – Mark Baker Aug 03 '15 at 15:48
  • Likewise for changes to `$val`, you're changing an existing value in an existing zval, so there's no need to create a new zval – Mark Baker Aug 03 '15 at 15:58
  • There is a [whole section in the manual](http://php.net/manual/en/internals2.variables.intro.php) about them. – Josh J Oct 16 '15 at 14:06
  • Even better, read these posts by nikic https://nikic.github.io/2015/05/05/Internal-value-representation-in-PHP-7-part-1.html He did a lot of work on internals for php 7 and gives some excellent information on how it works. – Josh J Oct 16 '15 at 14:25

1 Answers1

3

Not quite. Let's go through it line-by-line, covering how PHP 5 would handle these zvals.

<?php
$randomByteString = 'abcd';

A new zval containing 'abcd' is created and now $randomByteString points to it.

Zval count so far: 1

$val = 0;

A new zval containing 0 is created and now $val points to it.

Zval count so far: 2

for ($i = 0; $i < 4; ++$i) {

The first time we run through this, we create a zval for 0 and point $i to it.

Comparing $i and 4 might create a boolean zval temporarily. My memory of what PHP 5 does here is fuzzy, it might not do this under-the-hood. It doesn't matter for our overall count though, because it'd be thrown away immediately.

Incrementing $i doesn't create a zval, it just changes the number inside the zval $i already points to.

Zval count so far: 3 (not including temporary zval as it was thrown away)

    $val |= ord($randomByteString[$i]) << ($i * 8);

Accessing $randomByteString[$i] will create a zval containing a single-byte string. Passing it to ord() will discard that zval and create a new zval containing an integer.

Multiplying it 8 by $i will create a new zval.

Left shifting our ord() result by our $i result will create a new zval and chuck away our two input zvals.

Finally, using |= with $val will change the value in $val and chuck away our left shift result. $val already exists so we just change the contents rather than making a new zval for it.

Zval count so far: 4

}

echo $val;

Echo won't create any zvals.

At the end of the script, you have 4 zvals around.

By the way, the zvals created for $i < 4, $randomByteString[$i], ($i * 8) and << are all temporary zvals: they don't cause new memory allocations, instead they're stored in a special area of memory used for temporary values. This means that there's no performance penalty for creating these temporary values. The other zvals (including ord() for some reason, function calls are inefficient), on the other hand, do require memory allocations.

Also, this is all just for PHP 5. PHP 7 handles zvals a lot more smartly, so that usually there's no separate memory allocations needed for them.

Andrea
  • 19,134
  • 4
  • 43
  • 65