Protocol Buffers - Google's data interchange format (grpc依赖)
https://developers.google.com/protocol-buffers/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
112 lines
3.7 KiB
112 lines
3.7 KiB
|
|
# Refcounting Tips |
|
|
|
One of the trickiest parts of the C extension for PHP is getting the refcounting |
|
right. These are some notes about the basics of what you should know, |
|
especially if you're not super familiar with PHP's C API. |
|
|
|
These notes cover the same general material as [the Memory Management chapter of |
|
the PHP internal's |
|
book](https://www.phpinternalsbook.com/php7/zvals/memory_management.html), but |
|
calls out some points that were not immediately clear to me. |
|
|
|
## Zvals |
|
|
|
In the PHP C API, the `zval` type is roughly analogous to a variable in PHP, eg: |
|
|
|
```php |
|
// Think of $a as a "zval". |
|
$a = []; |
|
``` |
|
|
|
The equivalent PHP C code would be: |
|
|
|
```c |
|
zval a; |
|
ZVAL_NEW_ARR(&a); // Allocates and assigns a new array. |
|
``` |
|
|
|
PHP is reference counted, so each variable -- and thus each zval -- will have a |
|
reference on whatever it points to (unless its holding a data type that isn't |
|
refcounted at all, like numbers). Since the zval owns a reference, it must be |
|
explicitly destroyed in order to release this reference. |
|
|
|
```c |
|
zval a; |
|
ZVAL_NEW_ARR(&a); |
|
|
|
// The destructor for a zval, this must be called or the ref will be leaked. |
|
zval_ptr_dtor(&a); |
|
``` |
|
|
|
Whenever you see a `zval`, you can assume it owns a ref (or is storing a |
|
non-refcounted type). If you see a `zval*`, which is also quite common, then |
|
this is *pointing to* something that owns a ref, but it does not own a ref |
|
itself. |
|
|
|
The [`ZVAL_*` family of |
|
macros](https://github.com/php/php-src/blob/4030a00e8b6453aff929362bf9b25c193f72c94a/Zend/zend_types.h#L883-L1109) |
|
initializes a `zval` from a specific value type. A few examples: |
|
|
|
* `ZVAL_NULL(&zv)`: initializes the value to `null` |
|
* `ZVAL_LONG(&zv, 5)`: initializes a `zend_long` (integer) value |
|
* `ZVAL_ARR(&zv, arr)`: initializes a `zend_array*` value (refcounted) |
|
* `ZVAL_OBJ(&zv, obj)`: initializes a `zend_object*` value (refcounted) |
|
|
|
Note that all of our custom objects (messages, repeated fields, descriptors, |
|
etc) are `zend_object*`. |
|
|
|
The variants that initialize from a refcounted type do *not* increase the |
|
refcount. This makes them suitable for initializing from a newly-created object: |
|
|
|
```c |
|
zval zv; |
|
ZVAL_OBJ(&zv, CreateObject()); |
|
``` |
|
|
|
Once in a while, we want to initialize a `zval` while also increasing the |
|
reference count. For this we can use `ZVAL_OBJ_COPY()`: |
|
|
|
```c |
|
zend_object *some_global; |
|
|
|
void GetGlobal(zval *zv) { |
|
// We want to create a new ref to an existing object. |
|
ZVAL_OBJ_COPY(zv, some_global); |
|
} |
|
``` |
|
|
|
## Transferring references |
|
|
|
A `zval`'s ref must be released at some point. While `zval_ptr_dtor()` is the |
|
simplest way of releasing a ref, it is not the most common (at least in our code |
|
base). More often, we are returning the `zval` back to PHP from C. |
|
|
|
```c |
|
zval zv; |
|
InitializeOurZval(&zv); |
|
// Returns the value of zv to the caller and donates our ref. |
|
RETURN_COPY_VALUE(&zv); |
|
``` |
|
|
|
The `RETURN_COPY_VALUE()` macro (standard in PHP 8.x, and polyfilled in earlier |
|
versions) is the most common way we return a value back to PHP, because it |
|
donates our `zval`'s refcount to the caller, and thus saves us from needing to |
|
destroy our `zval` explicitly. This is ideal when we have a full `zval` to |
|
return. |
|
|
|
Once in a while we have a `zval*` to return instead. For example when we parse |
|
parameters to our function and ask for a `zval`, PHP will give us pointers to |
|
the existing `zval` structures instead of creating new ones. |
|
|
|
```c |
|
zval *val; |
|
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &val) == FAILURE) { |
|
return; |
|
} |
|
// Returns a copy of this zval, adding a ref in the process. |
|
RETURN_COPY(val); |
|
``` |
|
|
|
When we use `RETURN_COPY`, the refcount is increased; this is perfect for |
|
returning a `zval*` when we do not own a ref on it.
|
|
|