/*
|----------------------------------------
| Working with Variables
| @weiChen translate, <farwish@live.com>
| @MIT License
*/
http://php.net/manual/zh/internals2.variables.php
(中文doc,不完全)http://php.net/manual/zh/internals2.variables.intro.php http://php.net/manual/en/internals2.variables.intro.php
成为一个Hacker,对变量如何储存和操作的良好理解是必要的。引擎试图掩盖变量概念的复杂性,变量可以是任何类型, 通过提供一个一致和直观的用来访问结构体各个领域的宏设置。当Hacker通过本章工作时,他们应该变得对涉及PHP中变量的术语和概念 感到舒适。
Note: PHP是一个动态的,松散类型的语言,使用即写即拷和引用计数。
要弄清上面究竟是什么意思:PHP是一个高层语言,弱类型由引擎隐式引用转换,或者强制变量转为执行类型。 引用计数意思是引擎可以推断当一个变量在用户的代码里不再有任何引用时,可以释放和这个变量相关的结构体。
所有PHP中的变量表现为一个结构体,zval:
typedef struct _zval_struct {
zvalue_value value; /* variable value */
zend_uint refcount__gc; /* reference counter */
zend_uchar type; /* value type */
zend_uchar is_ref__gc; /* reference flag */
} zval;
译者注:
zval定义在Zend/zend.h中,
在 https://github.com/farwish/php-5.6.14-notes/blob/php-5.6.14-notes/Zend/zend.h 第334行
zvalue_value
是一个union,就定义在zval前面;zend_uint
和 zend_uchar
类型定义在 Zend/zend_types.h
中
)
zvalue_value 是一个联合体(union),可以代表所有类型的变量:
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len; /* this will always be set for strings */
} str; /* string (always has length) */
HashTable *ht; /* an array */
zend_object_value obj; /* stores an object store handle, and handlers */
} zvalue_value;
从上面的结构体应该清楚知道一个变量可以是一种类型,变量数据由 zval_value 联合体中的合适字段表示。zval 自身保存类型,引用计数和一个标记标示一个变量是否是一个引用。
-------------------------------------------------
| 原生类型常量 |
|-------------------------------------------------|
| 常量 映射 |
|-------------------------------------------------|
| IS_NULL 本例中没有设置值 |
|-------------------------------------------------|
| IS_LONG lval |
|-------------------------------------------------|
| IS_DOUBLE dval |
|-------------------------------------------------|
| IS_BOOL lval |
|-------------------------------------------------|
| IS_RESOURCE lval |
|-------------------------------------------------|
| IS_STRING str |
|-------------------------------------------------|
| IS_ARRAY ht |
|-------------------------------------------------|
| IS_OBJECT obj |
-------------------------------------------------
Note: 额外的常量帮助指定内部类型如常量数组和可调用的对象,它们的使用在文档这部分范围之外。
下面表格定义的宏由引擎暴露用来和 zval 值一起工作:
---------------------------------------------------------------------------------------------------------
| 宏访问器 |
|---------------------------------------------------------------------------------------------------------|
| 原型 访问器 描述 |
|---------------------------------------------------------------------------------------------------------|
| zend_uchar Z_TYPE(zval zv) type 返回 value 的类型 |
|---------------------------------------------------------------------------------------------------------|
| long Z_LVAL(zval zv) value.lval |
|---------------------------------------------------------------------------------------------------------|
| zend_bool Z_BVAL(zval zv) value.lval 传递long value 到 zend_bool |
|---------------------------------------------------------------------------------------------------------|
| double Z_DVAL(zval zv) value.dval |
|---------------------------------------------------------------------------------------------------------|
| long Z_RESVAL(zval zv) value.lval 为值返回资源列表标示符 |
|---------------------------------------------------------------------------------------------------------|
| char* Z_STRVAL(zval zv) value.str.val 返回字符串值 |
|---------------------------------------------------------------------------------------------------------|
| int Z_STRLEN(zval zv) value.str.len 返回字符串值的长度 |
|---------------------------------------------------------------------------------------------------------|
| HashTable* Z_ARRVAL(zval zv) value.ht 返回HashTable(array)值 |
|---------------------------------------------------------------------------------------------------------|
| zend_object_value Z_OBJVAL(zval zv) value.obj 返回对象值 |
|---------------------------------------------------------------------------------------------------------|
| uint Z_OBJ_HANDLE(zval zv) value.obj.handle 为对象值返回资源句柄 |
|---------------------------------------------------------------------------------------------------------|
| zend_object_handlers* Z_OBJ_HT_P(zval zv) value.obj.handlers 为对象值返回处理表 |
|---------------------------------------------------------------------------------------------------------|
| zend_class_entry* Z_OBJCE(zval zv) value.obj 为对象值返回类入口 |
|---------------------------------------------------------------------------------------------------------|
| HashTable* Z_OBJPROP(zval zv) value.obj 返回对象值的属性 |
|---------------------------------------------------------------------------------------------------------|
| HashTable* Z_OBJPROP(zval zv) value.obj 返回对象值得属性 |
|---------------------------------------------------------------------------------------------------------|
| HashTable* Z_OBJDEBUG(zval zv) value.obj 如果一个对象设置了get_debug_info处理器,|
| 它将被调用,否则 Z_OBJPROP 调用 |
---------------------------------------------------------------------------------------------------------
引用计数和引用如何工作的详细细节请查看引用计数基础章节(http://php.net/manual/en/features.gc.refcounting-basics.php)
-----------------------------------------------------------------------
| 引用计数操作 |
|-----------------------------------------------------------------------|
| 原型 描述 |
|-----------------------------------------------------------------------|
| zend_uint Z_REFCOUNT(zval zv) 返回引用计数的值 |
|-----------------------------------------------------------------------|
| zend_uint Z_SET_REFCOUNT(zval zv) 设置引用计数的值,并返回 |
|-----------------------------------------------------------------------|
| zend_uint Z_ADDREF(zval zv) 预增量的引用计数,并返回 |
|-----------------------------------------------------------------------|
| zend_uint Z_DELREF(zval zv) 预减值得引用计数,并返回 |
|-----------------------------------------------------------------------|
| zend_bool Z_ISREF(zval zv) 检测 zval 是否是一个引用 |
|-----------------------------------------------------------------------|
| void Z_UNSET_ISREF(zval zv) 设置 is_ref__gc 为0 |
|-----------------------------------------------------------------------|
| void Z_SET_ISREF(zval zv) 设置 is_ref__gc 为1 |
|-----------------------------------------------------------------------|
| void Z_SET_ISREF_TO(zval zv, zend_uchar to) 设置 is_ref__gc 为 to |
-----------------------------------------------------------------------
Note: 上面的Z_的宏都带有一个zval,它们都由_P作为后缀重新定义来带有一个指向zval的宏, 如 zend_uchar Z_TYPE_P(zval pzv),并且再一次以_PP为后缀带有指向zval的指针,如 zend_uchar Z_TYPE_PP(zval** ppzv)
-----------------------------------------------------------------------------------------------------------
| 创建,描述,分离和复制 |
|-----------------------------------------------------------------------------------------------------------|
| 原型 描述 |
|-----------------------------------------------------------------------------------------------------------|
| ALLOC_ZVAL(zval* pzval) 分配内存 pzval |
|-----------------------------------------------------------------------------------------------------------|
| ALLOC_INIT_ZVAL(zval* pzval) 分配内存 pzval, 并把pzval的指针指向null类型的zval |
|-----------------------------------------------------------------------------------------------------------|
| MAKE_STD_ZVAL(zval* pzval) 分配内存 pzval, 设置引用计数为1 |
|-----------------------------------------------------------------------------------------------------------|
| ZVAL_COPY_VALUE(zval* dst, zval* src) 用 src 的值和类型设置 dst 的值和类型 |
|-----------------------------------------------------------------------------------------------------------|
| INIT_PZVAL_COPY(zval* dst, zval* dst) 执行 ZVAL_COPY_VALUE,设置 dst 引用计数为1,设置 is_ref__gc 为0 |
|-----------------------------------------------------------------------------------------------------------|
| SEPARATE_ZVAL(zval** ppzval) 如果 ppzval 的引用计数 > 1,重新分配 *ppzval 到一个新的内存分配, |
| 副本,和构造相同类型和值的 zval |
|-----------------------------------------------------------------------------------------------------------|
| SEPARATE_ZVAL_IF_NOT_REF(zval** ppzval) 如果 *ppzval 不是一个引用,在 ppzval 上执行 SEPARATE_ZVAL |
|-----------------------------------------------------------------------------------------------------------|
| SEPARATE_ZVAL_TO_MAKE_IS_REF(zval** ppzval) 如果 *ppzval 不是一个引用,在 ppzval 上执行 SEPARATE_ZVAL |
| 接着 Z_SET_ISREF_PP |
|-----------------------------------------------------------------------------------------------------------|
| COPY_PZVAL_TO_ZVAL(zval dst, zval** src) 不影响 src 的引用计数的情况下把 dst 变成 src 的一个副本 |
|-----------------------------------------------------------------------------------------------------------|
| MAKE_COPY_ZVAL(zval** src, zval* dst) 执行 INIT_PZVAL_COPY,接着是新zval上的 zval_copy_ctor |
|-----------------------------------------------------------------------------------------------------------|
| void zval_copy_ctor(zval** pzval) 执行引用计数保持,广泛使用在整个引擎中 |
|-----------------------------------------------------------------------------------------------------------|
| void zval_ptr_dtor(zval* pzval) 递减变量的引用计数,如果没有引用计数就销毁变量 |
|-----------------------------------------------------------------------------------------------------------|
| FREE_ZVAL(zval* pzval) 释放 pzval |
-----------------------------------------------------------------------------------------------------------
Note:对象和资源有一个引用计数作为它们各自结构体的一部分,当 zval_ptr_dtor 没有被两者调用,它们的相关的 del_ref 方法 执行。更多信息查看Working with Objects 和 Working with Resources。
如果一个Hacker只能记住两个方法,它们应该是 zval_copy_ctor 和 zval_ptr_dtor,这些是引擎引用计数机制的基础, 并且重要的是记住 zval_copy_ctor 实际在正常情况不会发生任何复制,它简单提高引用数量。通过相同的token,只有当没有引用时 zval_ptr_dtor 才实际销毁一个变量并且引用计数为0。
PHP是弱类型的,如引擎为转换变量的类型到另一个类型提供了API函数。
-----------------------------------------------------------------------
| 类型转换 |
|-----------------------------------------------------------------------|
| 原型 |
|-----------------------------------------------------------------------|
| void convert_to_long(zval* pzval) |
|-----------------------------------------------------------------------|
| void convert_to_double(zval* pzval) |
|-----------------------------------------------------------------------|
| void convert_to_long_base(zval* pzval, int base) |
|-----------------------------------------------------------------------|
| void convert_to_null(zval* pzval) |
|-----------------------------------------------------------------------|
| void convert_to_boolean(zval* pzval) |
|-----------------------------------------------------------------------|
| void convert_to_array(zval* pzval) |
|-----------------------------------------------------------------------|
| void convert_to_object(zval* pzval) |
|-----------------------------------------------------------------------|
| void convert_object_to_type(zval* pzval, convert_func_t converter) |
-----------------------------------------------------------------------
Note:convert_func_t 函数应该有原型 (void) (zval* pzval)
到现在你应该有一个良好的理解:引擎的本地变量如何发现类型和读取 zval 值,如何操作引用计数,和其它 zval 标示。
数组存储在哈希表结构中,有 zval 类型 IS_ARRAY。创建,销毁和操作结构体变量的API函数都在这儿, 并可以在 Zend/zend_API.h 中找到。
-----------------------------------------------------------------------------------------
| 哈希表变量API |
|-----------------------------------------------------------------------------------------|
| 原型 描述 |
|-----------------------------------------------------------------------------------------|
| void array_init(zval* pzval) 初始化变量为哈希表,为哈希表设置类型和合适的销毁函数 |
|-----------------------------------------------------------------------------------------|
| void array_init_size(zval* pzval) 像 array_init 一样用最小尺寸的空间初始化变量 |
-----------------------------------------------------------------------------------------
Note:不要太努力寻找array_destroy:销毁一个变量数组,你应该在变量上调用 zval_ptr_dtor,如果没有其它的引用到变量, 数组将销毁。
-----------------------------------------------------------------
| 编入索引的数组API |
|-----------------------------------------------------------------|
| 原型 |
|-----------------------------------------------------------------|
| int add_index_long(zval* pzval, ulong index, long value) |
|-----------------------------------------------------------------|
| int add_index_null(zval* pzval, ulong index) |
|-----------------------------------------------------------------|
| int add_index_bool(zval* pzval, ulong index, zend_bool value) |
|-----------------------------------------------------------------|
| int add_index_bool(zval* pzval, ulong index, zend_bool value) |
|-----------------------------------------------------------------|
| int add_index_resource(zval* pzval, ulong index, uint value) |
|-----------------------------------------------------------------|
| int add_index_double(zval* pzval, ulong index, double value) |
|-----------------------------------------------------------------|
| int add_index_string(zval* pzval, ulong index, char* string, zend_bool duplicate) |
|-------------------------------------------------------------------------------------------------|
| int add_index_stringl(zval* pzval, ulong index, char* string, uint length, zend_bool duplicate) |
|-------------------------------------------------------------------------------------------------|
| int add_index_zval(zval* pzval, ulong index, zval* value) |
|-------------------------------------------------------------------------------------------------|
| int add_next_index_long(zval* pzval, long value) |
|-------------------------------------------------------------------------------------------------|
| int add_next_index_null(zval* pzval) |
|-------------------------------------------------------------------------------------------------|
| int add_next_index_bool(zval* pzval, zend_bool value) |
|-------------------------------------------------------------------------------------------------|
| int add_next_index_resource(zval* pzval, uint value) |
|-------------------------------------------------------------------------------------------------|
| int add_next_index_double(zval* pzval, double value) |
|-------------------------------------------------------------------------------------------------|
| int add_next_index_string(zval* pzval, const char* string, zend_bool dulpicate) |
|-------------------------------------------------------------------------------------------------|
| int add_next_index_stringl(zval* pzval, const char* string, uint length, zend_bool duplicate) |
|-------------------------------------------------------------------------------------------------|
| int add_next_index_zval(zval* pzval, zval* value) |
-------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------
| 关联数组API |
|---------------------------------------------------------------------------------------------------------|
| 原型 |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_long(zval* pzval, const char* key, long value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_long_ex(zval* pzval, const char* key, uint klen, long value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_null(zval* pzval, const char* key) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_null_ex(zval* pzval, const char* key, uint klen) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_bool(zval* pzval, const char* key, zend_bool value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_bool(zval* pzval, const char* key, zend_bool value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_bool_ex(zval* pzval, const char* key, uint klen, zend_bool value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_resource(zval* pzval, const char* key, uint value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_resource_ex(zval* pzval, const char* key, uint klen, uint value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_double(zval* pzval, const char* key, double value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_double_ex(zval* pzval, const char* key, uint klen, double value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_string(zval* pzval, const char* key, const char* value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_string_ex(zval* pzval, const char* key, uint klen, const char* value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_stringl(zval* pzval, const char* key, const char* value, uint vlen, zend_bool duplicate) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_stringl_ex(zval* pzval, const char* key, uint klen, const char* value, uint vlen, |
| zend_bool duplicate) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_zval(zval* pzval, const char* key, zval* value) |
|---------------------------------------------------------------------------------------------------------|
| int add_assoc_zval_ex(zval* pzval, const char* key, uint klen, zval* value) |
---------------------------------------------------------------------------------------------------------
Note:add_*_string 函数接收一个叫 duplicate 的参数,当 duplicate 为true时会复制 estrndup 字符串。
Note:add_*_zval 函数不调整参数值的引用计数。
为了在数组变量上执行更多高级的选项,我们必须直接使用哈希表API。
哈希表结构在PHP中提供许多用途,并且可以在任何地方看到,对它的功能的良好的理解对成为好Hacker是必要的。
引擎实现的哈希表是一个标准的哈希表,那就是说,一个基于 key => value 的存储,keys永远是字符串, 哈希由zend_inline_hash_func(const char* key, uint length)哈希算法计算出,结果是好的分布和合理的用法。
哈希表的内部结构和准确操作超出了这篇文档范围,文档告诉你可用的API和如何最好的使用它们。 更多哈希表的细节见 Zend/zend_hash.h。
除非另有说明,这些函数都返回 FAILURE 当请求失败时,否则返回 SUCCESS。
Note:重要的是记住,通常哈希表API函数要求的key长度是以null终止的字符长度,包括null终止符,换句话说, 准确的是 strlen(key) + 1
下面是一些你需要知道的typedef,当和哈希表交互的时候。它们大部分是自定义的并且让Hacker完全准确的理解下面的原型。
typedef ulong (*hash_func_t)(const char *arKey, uint nKeyLength); /* 大部分是多余的 */
typedef int (*compare_func_t)(const void *, const void * TSRMLS_DC); /* 比较方法 */
typedef void (*sort_func_t)(void *, site_t, register size_t, compare_func_t TSRMLS_DC); /* 排序方法 */
typedef void (*dtor_func_t)(void *pDest); /* 销毁器方法 */
typedef void (*copy_ctor_func_t)(void *pElement); /* 复制构造器 */
typedef void (*copy_ctor_param_func_t)(void *pElement, void *pParam); /* 带参数的复制构造器 */
typedef int (*apply_func_t)(void *pDest TSRMLS_DC); /* 应用方法 */
typedef int (*apply_func_arg_t)(void *pDest, void *argument TSRMLS_DC); /* 带参数的应用方法 */
typedef int (*apply_func_args_t)(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key); /* 带多个参数的应用方法*/
哈希表API
--------------------------------------------------------------------------------------------------------------
| int zend_hash_init(HashTable* ht, uint size, hash_func_t hash, dtor_func_t destructor, zend_bool persistent) |
| 初始化哈希表来存储至少 size 个元素,hash 由于历史原因一直存在并且总是被忽略, |
| zend_inline_hash_func 总是被当做散列方法使用。destructor 可以是NULL。 |
|--------------------------------------------------------------------------------------------------------------|
| int zend_hash_add(HashTable* ht, const char* key, uint klen, void* data, uint dlen, void** dest) |
| 使用指定的 key 添加数据到表格中,key 是 length 字节长(会从 key 复制到 table)。 |
| 如果 key 设为 FAILURE 会被返回。 |
|--------------------------------------------------------------------------------------------------------------|
| int zend_hash_update(HashTable* ht, const char* key, uint klen, void* data, uint dlen, void** dest) |
| 使用指定的 key 添加数据到表格中,key 是 length 字节长(会从 key 复制到 table)。如果 key 之前被设置过, |
| 有在 dtor_func_t 上面调用的旧数据并且已存在的数据由 data 替换。 |
|--------------------------------------------------------------------------------------------------------------|
| int zend_hash_find(HashTable* ht, const char* key, uint klen, void** data) |
| 在table中查找 key,如果找到则设置 *data 并返回 SUCCESS。 |
|--------------------------------------------------------------------------------------------------------------|
| zend_bool zend_hash_exists(HashTable* ht, const char* key, uint klen) |
| 如果在 ht 中找到 key,积极返回。 |
|--------------------------------------------------------------------------------------------------------------|
| int zend_hash_del(HashTable* ht, const char* key, uint klen) |
| 如果在table中找到,移除由 key 指定的入口。 |
|--------------------------------------------------------------------------------------------------------------|
| int zend_hash_index_update(HashTable* ht, ulong index, void* data, uint dsize, void** dest) |
| 用 data 中的数据更新 ht 在 index 上的入口。 |
|--------------------------------------------------------------------------------------------------------------|
| int zend_hash_index_del(HashTable* ht, ulong index) |
| 删除 ht 在 index 的入口。 |
|--------------------------------------------------------------------------------------------------------------|
| int zend_hash_index_find(HashTable* ht, ulong index, void** data) |
| 重定向 *data 到 ht 中 index 指定的data。 |
|--------------------------------------------------------------------------------------------------------------|
| int zend_hash_index_exists(HashTable* ht, ulong index) |
| 如果 index 在 ht 中,积极返回。 |
|--------------------------------------------------------------------------------------------------------------|
| ulong zend_hash_next_free_element(HashTable* ht) |
| 返回 ht 下一个空元素的index. |
--------------------------------------------------------------------------------------------------------------
小心:接收 void* data 的 zend_hash_* 函数应该通常描述为 (void**)(ie, (void**)&data)
遍历哈希表通常是必要的,要做这件事你需要提供一个指针到哈希表, 带一个可选的HashPosition - 一个哈希表API的结构允许遍历不影响哈希表的内部位置。 下面提供遍历函数,和一个使用例子。
哈希表遍历API
-----------------------------------------------------------------------------------------------------------
| int zend_hash_internal_pointer_reset(HashTable* ht) |
| 重置 `ht` 内部指针到开始位置 |
|-----------------------------------------------------------------------------------------------------------|
| int zend_hash_internal_pointer_reset_ex(HashTable* ht, HashPosition position) |
| 设置 `position` 为 `ht` 的开始位置 |
|-----------------------------------------------------------------------------------------------------------|
| int zend_hash_get_current_data(HashTable* ht, void* data) |
| 获取 `ht` 当前位置的数据,数据应该被转换为void**,如:(void**) &data |
|-----------------------------------------------------------------------------------------------------------|
| int zend_hash_get_current_data_ex(HashTable* ht void* data, HashPosition position) |
| 设置 `data` 到 ht 中 `position` 的数据 |
|-----------------------------------------------------------------------------------------------------------|
| int zend_hash_get_current_key(HashTable* ht, void* data, char** key, uint klen, ulong index, |
| zend_bool duplicate) |
| 在当前位置的key信息中设置 `key` , `klen` , 和 `index`。 |
| HASH_KEY_IS_STRING 和 HASH_KEY_IS_LONG 可能返回的值表示是当前位置找到的key的种类。 |
|-----------------------------------------------------------------------------------------------------------|
| int zend_hash_get_current_key_ex(HashTable* ht, void* data, char** key, uint klen, ulong index, |
| zend_bool duplicate, HashPosition position) |
| 在 `position` 的key信息中设置`key` , `klen` , 和 `index`。 |
| 返回的值 HASH_KEY_IS_STRING 和 HASH_KEY_IS_LONG 表示 `position` 处的key的种类。 |
|-----------------------------------------------------------------------------------------------------------|
| int zend_hash_move_forward(HashTable* ht) |
| 移动 `ht` 的内部指针到下一个 `ht` 的入口 |
|-----------------------------------------------------------------------------------------------------------|
| int zend_hash_move_forward_ex(HashTable* ht, HashPosition position) |
| 移动 `position` 到 `ht` 的下一个入口 |
-----------------------------------------------------------------------------------------------------------
上面的函数使遍历一个哈希表更像一个正常的循环,就像这样:
HashPosition position;
zval **data = NULL;
for (zend_hash_internal_pointer_reset_ex(ht, &position);
zend_hash_get_current_data_ex(ht, (void**) &data, &position) == SUCCESS;
zend_hash_move_forward_ex(ht, &position)) {
/* by now we have data set and can use Z_ macros for accessing type and variable data */
char *key = NULL;
uint klen;
ulong index;
if (zend_hash_get_current_key_ex(ht, &key, &klen, &index, 0, &position) == HASH_KEY_IS_STRING) {
/* the key is a string, key and klen will be set */
} else {
/* we assume the key to be long, index will be set */
}
}
Note: 如果一个哈希表已经被传递到引擎中,使用 zend_hash_*_ex API 来防止在用户空间中未知的行为是个好主意。
Note: 如果一个key被请求复制并且返回 HAS_KEY_IS_STRING,调用者必须 efree
这个复制的key。
------------------------------------------------------------------------------------------------------
| 复制,合并 和 排序 |
| -----------------------------------------------------------------------------------------------------|
| void zend_hash_copy(HashTable* target, HashTable* source, copy_ctor_func_t pCopyConstructor, |
| void* tmp, uint size) |
| 把 `source` 的内容复制到`target`。`tmp`应该是合适类型的一个未分配的临时指针,在复制时使用。 |
| `size` 是每个元素的大小. |
|------------------------------------------------------------------------------------------------------|
| void zend_hash_merge(HashTable* target, HashTable* source, copy_ctor_func_t pCopyConstractor, |
| void* tmp, uint size, zend_bool overwrite) |
| 合并 `source` 的内容到目的地,`overwrite`为true的地方替换已存在的入口 |
|------------------------------------------------------------------------------------------------------|
| void int zend_hash_sort(HashTable* ht, sort_func_t sort_func, compare_func_t compare_func, |
| int renumber TSRMLS_DC) |
| `renumber` 控制索引是否应该保持,查看本节开始的 typedef 获得更多信息。 |
------------------------------------------------------------------------------------------------------
Note: 当一个函数接受一个 copy_ctor_func_t pCopyConstractor
, 函数通过在韩系表中创建每一个条目来执行。
在引擎中最通用的复制构造器是一个围绕 zval_copy_ctor
的包装器,宏 ZVAL_COPY_CTOR
。