Joshua Haberman | b0d90e3 | 2021-05-13 23:16:49 -0700 | [diff] [blame] | 1 | |
| 2 | # Refcounting Tips |
| 3 | |
| 4 | One of the trickiest parts of the C extension for PHP is getting the refcounting |
| 5 | right. These are some notes about the basics of what you should know, |
| 6 | especially if you're not super familiar with PHP's C API. |
| 7 | |
| 8 | These notes cover the same general material as [the Memory Management chapter of |
| 9 | the PHP internal's |
| 10 | book](https://www.phpinternalsbook.com/php7/zvals/memory_management.html), but |
| 11 | calls out some points that were not immediately clear to me. |
| 12 | |
| 13 | ## Zvals |
| 14 | |
| 15 | In the PHP C API, the `zval` type is roughly analogous to a variable in PHP, eg: |
| 16 | |
| 17 | ```php |
| 18 | // Think of $a as a "zval". |
| 19 | $a = []; |
| 20 | ``` |
| 21 | |
| 22 | The equivalent PHP C code would be: |
| 23 | |
| 24 | ```c |
| 25 | zval a; |
| 26 | ZVAL_NEW_ARR(&a); // Allocates and assigns a new array. |
| 27 | ``` |
| 28 | |
| 29 | PHP is reference counted, so each variable -- and thus each zval -- will have a |
| 30 | reference on whatever it points to (unless its holding a data type that isn't |
| 31 | refcounted at all, like numbers). Since the zval owns a reference, it must be |
| 32 | explicitly destroyed in order to release this reference. |
| 33 | |
| 34 | ```c |
| 35 | zval a; |
| 36 | ZVAL_NEW_ARR(&a); |
| 37 | |
| 38 | // The destructor for a zval, this must be called or the ref will be leaked. |
| 39 | zval_ptr_dtor(&a); |
| 40 | ``` |
| 41 | |
| 42 | Whenever you see a `zval`, you can assume it owns a ref (or is storing a |
| 43 | non-refcounted type). If you see a `zval*`, which is also quite common, then |
| 44 | this is *pointing to* something that owns a ref, but it does not own a ref |
| 45 | itself. |
| 46 | |
| 47 | The [`ZVAL_*` family of |
| 48 | macros](https://github.com/php/php-src/blob/4030a00e8b6453aff929362bf9b25c193f72c94a/Zend/zend_types.h#L883-L1109) |
| 49 | initializes a `zval` from a specific value type. A few examples: |
| 50 | |
| 51 | * `ZVAL_NULL(&zv)`: initializes the value to `null` |
| 52 | * `ZVAL_LONG(&zv, 5)`: initializes a `zend_long` (integer) value |
| 53 | * `ZVAL_ARR(&zv, arr)`: initializes a `zend_array*` value (refcounted) |
| 54 | * `ZVAL_OBJ(&zv, obj)`: initializes a `zend_object*` value (refcounted) |
| 55 | |
| 56 | Note that all of our custom objects (messages, repeated fields, descriptors, |
| 57 | etc) are `zend_object*`. |
| 58 | |
| 59 | The variants that initialize from a refcounted type do *not* increase the |
| 60 | refcount. This makes them suitable for initializing from a newly-created object: |
| 61 | |
| 62 | ```c |
| 63 | zval zv; |
| 64 | ZVAL_OBJ(&zv, CreateObject()); |
| 65 | ``` |
| 66 | |
| 67 | Once in a while, we want to initialize a `zval` while also increasing the |
| 68 | reference count. For this we can use `ZVAL_OBJ_COPY()`: |
| 69 | |
| 70 | ```c |
| 71 | zend_object *some_global; |
| 72 | |
| 73 | void GetGlobal(zval *zv) { |
| 74 | // We want to create a new ref to an existing object. |
| 75 | ZVAL_OBJ_COPY(zv, some_global); |
| 76 | } |
| 77 | ``` |
| 78 | |
| 79 | ## Transferring references |
| 80 | |
| 81 | A `zval`'s ref must be released at some point. While `zval_ptr_dtor()` is the |
| 82 | simplest way of releasing a ref, it is not the most common (at least in our code |
| 83 | base). More often, we are returning the `zval` back to PHP from C. |
| 84 | |
| 85 | ```c |
| 86 | zval zv; |
| 87 | InitializeOurZval(&zv); |
| 88 | // Returns the value of zv to the caller and donates our ref. |
| 89 | RETURN_COPY_VALUE(&zv); |
| 90 | ``` |
| 91 | |
| 92 | The `RETURN_COPY_VALUE()` macro (standard in PHP 8.x, and polyfilled in earlier |
| 93 | versions) is the most common way we return a value back to PHP, because it |
| 94 | donates our `zval`'s refcount to the caller, and thus saves us from needing to |
| 95 | destroy our `zval` explicitly. This is ideal when we have a full `zval` to |
| 96 | return. |
| 97 | |
| 98 | Once in a while we have a `zval*` to return instead. For example when we parse |
| 99 | parameters to our function and ask for a `zval`, PHP will give us pointers to |
| 100 | the existing `zval` structures instead of creating new ones. |
| 101 | |
| 102 | ```c |
| 103 | zval *val; |
| 104 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &val) == FAILURE) { |
| 105 | return; |
| 106 | } |
| 107 | // Returns a copy of this zval, adding a ref in the process. |
| 108 | RETURN_COPY(val); |
| 109 | ``` |
| 110 | |
| 111 | When we use `RETURN_COPY`, the refcount is increased; this is perfect for |
| 112 | returning a `zval*` when we do not own a ref on it. |