1 2# Refcounting Tips 3 4One of the trickiest parts of the C extension for PHP is getting the refcounting 5right. These are some notes about the basics of what you should know, 6especially if you're not super familiar with PHP's C API. 7 8These notes cover the same general material as [the Memory Management chapter of 9the PHP internal's 10book](https://www.phpinternalsbook.com/php7/zvals/memory_management.html), but 11calls out some points that were not immediately clear to me. 12 13## Zvals 14 15In 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 22The 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 29PHP is reference counted, so each variable -- and thus each zval -- will have a 30reference on whatever it points to (unless its holding a data type that isn't 31refcounted at all, like numbers). Since the zval owns a reference, it must be 32explicitly 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 42Whenever you see a `zval`, you can assume it owns a ref (or is storing a 43non-refcounted type). If you see a `zval*`, which is also quite common, then 44this is *pointing to* something that owns a ref, but it does not own a ref 45itself. 46 47The [`ZVAL_*` family of 48macros](https://github.com/php/php-src/blob/4030a00e8b6453aff929362bf9b25c193f72c94a/Zend/zend_types.h#L883-L1109) 49initializes 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 56Note that all of our custom objects (messages, repeated fields, descriptors, 57etc) are `zend_object*`. 58 59The variants that initialize from a refcounted type do *not* increase the 60refcount. This makes them suitable for initializing from a newly-created object: 61 62```c 63 zval zv; 64 ZVAL_OBJ(&zv, CreateObject()); 65``` 66 67Once in a while, we want to initialize a `zval` while also increasing the 68reference count. For this we can use `ZVAL_OBJ_COPY()`: 69 70```c 71zend_object *some_global; 72 73void 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 81A `zval`'s ref must be released at some point. While `zval_ptr_dtor()` is the 82simplest way of releasing a ref, it is not the most common (at least in our code 83base). 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 92The `RETURN_COPY_VALUE()` macro (standard in PHP 8.x, and polyfilled in earlier 93versions) is the most common way we return a value back to PHP, because it 94donates our `zval`'s refcount to the caller, and thus saves us from needing to 95destroy our `zval` explicitly. This is ideal when we have a full `zval` to 96return. 97 98Once in a while we have a `zval*` to return instead. For example when we parse 99parameters to our function and ask for a `zval`, PHP will give us pointers to 100the 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 111When we use `RETURN_COPY`, the refcount is increased; this is perfect for 112returning a `zval*` when we do not own a ref on it. 113