Symbol table and Array API 符号表和数组API

  1. 符号表
  2. Array API
  3. 参考部分

Zend哈希表API允许你使用任何类型的值,但绝大多数情况下是变量容器(zval)。使用zend_hashAPI和变量容器往往是有点麻烦,因为要处理zval的分配和初始化。这就是为什么PHP提供了第二套专门针对这个用例的API。在介绍这些简化的API前,我们先看看一种特殊的哈希表,PHP数组的使用。

说白了,PHP的数组实现中,相同值的字符串键和整型键存储同一个值的实现就基于此。

符号表

PHP的设计背后的核心概念是包含整数整数和字符串应该是可以相互转换的。这也就是说有键名为42“42”应该被认为是相同的数组。当然在哈希表的实现中是完全相反的:他们严格区分键的类型,在表中可以同时存在键42“42”,值也不同。

这就是为什么有一个额外的symtable(符号表)API,这是一层围绕哈希表字符串键转换为整数键核心功能的包装。例如,zend_symtable_find()函数定义:

static inline int zend_symtable_find(
HashTable *ht, const char *arKey, uint nKeyLength, void **pData
) {
ZEND_HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_index_find(ht, idx, pData));
return zend_hash_find(ht, arKey, nKeyLength, pData);
}

此处不考虑ZEND_HANDLE_NUMERIC()宏的实现细节,只关注它背后的功能是非常重要的:如果arKey包含LONG_MINLONG_MAX之间的十进制整数,则该整数写入idxzend_hash_index_find()调用它。在其他情况下,代码将继续执行下一行,其中zend_hash_find()被调用。

除了zend_symtable_find()下功能是symtable API的一部分,再以相同的行为作为自己的哈希表的同行,但包括字符串到整数规范化:

static inline int zend_symtable_exists(HashTable *ht, const char *arKey, uint nKeyLength);
static inline int zend_symtable_del(HashTable *ht, const char *arKey, uint nKeyLength);
static inline int zend_symtable_update(
HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest
);
static inline int zend_symtable_update_current_key_ex(
HashTable *ht, const char *arKey, uint nKeyLength, int mode, HashPosition *pos
);

此外,还有两个用来创建符号表的宏:

#define ZEND_INIT_SYMTABLE_EX(ht, n, persistent) \
zend_hash_init(ht, n, NULL, ZVAL_PTR_DTOR, persistent)
#define ZEND_INIT_SYMTABLE(ht) \
ZEND_INIT_SYMTABLE_EX(ht, 2, 0)

你可以看到,这些宏只是在zend_hash_init()中被调用,ZVAL_PTR_DTOR在析构函数中被用到。所以这些宏跟整型字符串的转换行为没有直接关系。

让我们来个尝试:

HashTable *myht;
zval *zv1, *zv2;
zval **zv_dest;
ALLOC_HASHTABLE(myht);
ZEND_INIT_SYMTABLE(myht);
MAKE_STD_ZVAL(zv1);
ZVAL_STRING(zv1, "zv1", 1);
MAKE_STD_ZVAL(zv2);
ZVAL_STRING(zv2, "zv2", 1);
zend_hash_index_update(myht, 42, &zv1, sizeof(zval *), NULL);
zend_symtable_update(myht, "42", sizeof("42"), &zv2, sizeof(zval *), NULL);
if (zend_hash_index_find(myht, 42, (void **) &zv_dest) == SUCCESS) {
php_printf("Value at key 42 is %Z\n", *zv_dest);
}
if (zend_symtable_find(myht, "42", sizeof("42"), (void **) &zv_dest) == SUCCESS) {
php_printf("Value at key \"42\" is %Z\n", *zv_dest);
}
zend_hash_destroy(myht);
FREE_HASHTABLE(myht);

上面的代码将会输出:

Value at key 42 is zv2
Value at key "42" is zv2

两次update的调用写入了相同的元素(第二次复写了第一次),两次find的调用也找到了相同的元素。

Array API

现在,我们看数组API。这个API不直接作用于哈希表,而是通过Z_ARRVAL_P提取的哈希表接受zvals。

API前两个函数:array_init(),array_init_size(),它们的作用是初始化一个哈希表成为zval。前者只需要目标的zval,而后者则额外需要一个大小参数:

/* Create empty array into return_value */
array_init(return_value);
/* Create empty array with expected size 1000000 into return_value */
array_init_size(return_value, 1000000);

剩下的函数全都是处理插入值到一个数组的这种。有四组相似操作的函数:

/* Insert at next index */
int add_next_index_*(zval *arg, ...);
/* Insert at specific index */
int add_index_*(zval *arg, ulong idx, ...);
/* Insert at specific key */
int add_assoc_*(zval *arg, const char *key, ...);
/* Insert at specific key of length key_len (for binary safety) */
int add_assoc_*_ex(zval *arg, const char *key, uint key_len, ...);

这里*...是占位符类型参数。他们的有效值列在下表中:

Type Additional arguments
null none
long long n
double double d
string const char *str, int duplicate
stringl const char *str, uint length, int duplicate
resourse int r
zval zval *value

让我们来一个例子介绍这些函数的使用方法,让我们创建一个有不同类型键的数组:

PHP_FUNCTION(make_array) {
zval *zv;
array_init(return_value);
add_index_long(return_value, 10, 100);
add_index_double(return_value, 20, 3.141);
add_index_string(return_value, 30, "foo", 1);
add_next_index_bool(return_value, 1);
add_next_index_stringl(return_value, "\0bar", sizeof("\0bar")-1, 1);
add_assoc_null(return_value, "foo");
add_assoc_long(return_value, "bar", 42);
add_assoc_double_ex(return_value, "\0bar", sizeof("\0bar"), 1.61);
/* For some things you still have to manually create a zval... */
MAKE_STD_ZVAL(zv);
object_init(zv);
add_next_index_zval(return_value, zv);
}

var_dump()输出这个数组如下(NUL字节由\0替换):

array(9) {
[10]=>
int(100)
[20]=>
float(3.141)
[30]=>
string(3) "foo"
[31]=>
bool(true)
[32]=>
string(4) "\0bar"
["foo"]=>
NULL
["bar"]=>
int(42)
["\0bar"]=>
float(1.61)
[33]=>
object(stdClass)#1 (0) {
}
}

看上面的代码,你可能会注意到数组 API 是字符串长度更相矛盾: 传递给 _ex 函数的键长度,包括终止的 NUL 字节,而传递给 stringl 函数的字符串长度不包括 NUL 字节。

此外应指出的是,虽然这些函数以add开头,但他们更像更新功能,因为他们会覆盖以前现有键。

有几个额外的 add_get 函数都插入一个值并再次获取 (类似于 zend_hash_update 函数的最后一个参数)。几乎从不使用它们,也不会在这里讨论出于完整性的考虑。

哈希表、 符号表和数组 的Api 之旅就此结束。

参考部分

SYMTABLE AND ARRAY API—-PHP Internals Book

符号表

PHP ZVALS AND SYMBOL TABLE(S)

script>