blob: 26ca7170df7a754ad2945d43737be79dfab13f25 [file] [log] [blame] [view]
Joshua Habermanb0d90e32021-05-13 23:16:49 -07001
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.