PHP variables under the hood

sergeypodgornyy

Sergey Podgornyy

Posted on December 18, 2019

PHP variables under the hood

Variables in PHP are some containers that store the type of the variable, its value, the amount of referring variables to this container, and the flag - whether this variable is referenced.

PHP variables under the hood

Structures and Pointers

Structures are very similar to classes, but they can not have methods, just data, pointers to data and pointers to functions. Declaring a structure in C, you define the data type. And when defining a variable, you can write the name of this structure in place of the type of that variable, like so:

my_super_struct super_struct_instance;
Enter fullscreen mode Exit fullscreen mode

Pointers are like variables, but their values store an address in memory. Reference variables behave like dereferenced pointers, that means they access values of the pointer. Let's look at an example:

// defining pointer `foo`, that will points to the variable with `int` type
int *foo;
// defining variable with `int` type
int bar = 3;

// taking reference to variable `bar` and assigning it to pointer.
// `foo` stores memory address, where `bar` stores
foo = &bar;

// with an asterisk we dereference the pointer (take the value at its address) and increment the value
(*foo)++;

// we increment the pointer itself, that means the pointer will refer at another value
foo++;
Enter fullscreen mode Exit fullscreen mode

Dealing with pointers

Containers

The container is a structure called zval (short for “Zend value”), it represents an arbitrary PHP value and looks like this:

struct zval {
    zvalue_value value;
    zend_uchar type;
    zend_uchar is_ref;
    zend_ushort refcount;
};
Enter fullscreen mode Exit fullscreen mode

As we can see, there is a value, a type, a flag and an amount of referring variables. PHP zval supports the following 8 types:

  • BOOL
  • LONG (signed integer type)
  • DOUBLE (used to store floating point numbers)
  • STRING
  • ARRAY
  • OBJECT
  • RESOURCE
  • NULL

zvalue_value is an union. A union is a special type that may have several members declarations of different types, but only one will be used. That how it defines:

typedef union _zvalue_value {
    long lval; // integer
    double dval; // float
    struct {
        char *val;
        int len;
    } str; // string
    HashTable *ht; // array
    zend_object obj; // object
} zvalue_value;
Enter fullscreen mode Exit fullscreen mode

As a result, when you create a variable of this type, it will take in memory exactly as much as occupies the heaviest element of unions.

PHP variable creation

Why do we need all this

First, let's figure out why we need refcount. That's very simple: when you assign to a variable value of another variable, they both refer to one zval, and refcount increments.

2 variables refers to the same memory place

Now, if you change the value of one of these variables, PHP seeing the refcount greater than 1, will copy this zval, make the changes there, and your variable will point already to the new zval. It will look something like this:

PHP Under the hood
$foo = "baz";
$bar = $foo;
bar,foo: {
    type: string,
    value:
        str:
            val: "baz"
            len: 3
    is_ref: 0
    refcount: 2
}
$bar .= "q";
foo: {
    type: string,
    value:
        str:
            val: "baz"
            len: 3
    is_ref: 0
    refcount: 1
}
bar: {
    type: string,
    value:
        str:
            val: "bazq"
            len: 4
    is_ref: 0
    refcount: 1
}

This technique is called "copy on write" and it allows to reduce memory consumption quite well. Also, refcount is needed for the garbage collector, which removes from memory all zvals, which have refcount = 0.

And what happens with references? And is_ref is working? That's very simple: if you create a reference from a variable, the is_ref flag becomes 1, and the above optimization for this zval will not be applied.

PHP Under the hood
$foo = "baz";
$bar = $foo;
bar,foo: {
    type: string,
    value:
        str:
            val: "baz"
            len: 3
    is_ref: 0
    refcount: 2
}
$baz = &$foo;
baz,foo: {
    type: string,
    value:
        str:
            val: "baz"
            len: 3
    is_ref: 1
    refcount: 2
}
bar: { // variable `bar` was allocated to a separate `zval`
    type: string,
    value:
        str:
            val: "baz"
            len: 3
    is_ref: 0
    refcount: 1
}
$qwe = $foo;
baz,foo: {
    type: string,
    value:
        str:
            val: "baz"
            len: 3
    is_ref: 1
    refcount: 2
}
bar: {
    type: string,
    value:
        str:
            val: "baz"
            len: 3
    is_ref: 0
    refcount: 1
}
// this variable was also allocated to a separate `zval`
qwe: {
    type: string,
    value:
        str:
            val: "baz"
            len: 3
    is_ref: 0
    refcount: 1
}

Interesting in reading more about PHP? Follow this link to find more articles 😉

💖 💪 🙅 🚩
sergeypodgornyy
Sergey Podgornyy

Posted on December 18, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

PHP variables under the hood
php PHP variables under the hood

December 18, 2019