先定一个flag,以后争取每天理解一个php函数源码,熟悉php内核。
函数array_key_exists等数组相关的函数都定义在源码ext/standard/array.c文件中。
array_key_exists函数的作用是检测给定数组array中是否有键为key的值。源码如下:
PHP_FUNCTION(array_key_exists)
{
zval *key; /* key to check for */
HashTable *array; /* array to check in */
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_ZVAL(key)
Z_PARAM_ARRAY_OR_OBJECT_HT(array)
ZEND_PARSE_PARAMETERS_END();
switch (Z_TYPE_P(key)) {
case IS_STRING:
if (zend_symtable_exists_ind(array, Z_STR_P(key))) {
RETURN_TRUE;
}
RETURN_FALSE;
case IS_LONG:
if (zend_hash_index_exists(array, Z_LVAL_P(key))) {
RETURN_TRUE;
}
RETURN_FALSE;
case IS_NULL:
if (zend_hash_exists_ind(array, ZSTR_EMPTY_ALLOC())) {
RETURN_TRUE;
}
RETURN_FALSE;
default:
php_error_docref(NULL, E_WARNING, "The first argument should be either a string or an integer");
RETURN_FALSE;
}
}
如上,php7中采用zend_parse_parameters_start来代替zend_parse_parameters函数获取参数值,具体实现我还没有弄明白,后面补上,反正作用和zend_parse_parameters一样,之前也有写关于它的博客。
指针变量key指向一个zval结构体,通过判断这个key是字符串类型还是整型分别处理。
如果key指向的zval变量是string类型:
调用函数zend_symtabl_exists_ind函数,传入的第二个参数Z_STR_P(key)展开就是*(key).value.str,不熟悉zval结构的可以自行google一下。函数zend_symtabl_exists_ind定义在zend_hash.h中,代码如下:
static zend_always_inline int zend_symtable_exists_ind(HashTable *ht, zend_string *key)
{
zend_ulong idx;
if (ZEND_HANDLE_NUMERIC(key, idx)) {
return zend_hash_index_exists(ht, idx);
} else {
return zend_hash_exists_ind(ht, key);
}
}
这里面再次先通过函数zend_handle_numeric对key做判断,看这个字符串是不是数字类型的,比如说‘1234’,‘23’这种字符串。判断代码如下:
static zend_always_inline int _zend_handle_numeric_str(const char *key, size_t length, zend_ulong *idx)
{
const char *tmp = key;
if (*tmp > '9') {
return 0;
} else if (*tmp < '0') {
if (*tmp != '-') {
return 0;
}
tmp++;
if (*tmp > '9' || *tmp < '0') {
return 0;
}
}
return _zend_handle_numeric_str_ex(key, length, idx);
}
这里比较*tmp > '9'其实是比较的ascii码。如果key确实是字符串数字,那么返回函数zend_handle_numeric_str_ex的结果:
ZEND_API int ZEND_FASTCALL _zend_handle_numeric_str_ex(const char *key, size_t length, zend_ulong *idx)
{
register const char *tmp = key;
const char *end = key + length;
if (*tmp == '-') {
tmp++;
}
if ((*tmp == '0' && length > 1) /* numbers with leading zeros */
|| (end - tmp > MAX_LENGTH_OF_LONG - 1) /* number too long */
|| (SIZEOF_ZEND_LONG == 4 &&
end - tmp == MAX_LENGTH_OF_LONG - 1 &&
*tmp > '2')) { /* overflow */
return 0;
}
*idx = (*tmp - '0');
while (1) {
++tmp;
if (tmp == end) {
if (*key == '-') {
if (*idx-1 > ZEND_LONG_MAX) { /* overflow */
return 0;
}
*idx = 0 - *idx;
} else if (*idx > ZEND_LONG_MAX) { /* overflow */
return 0;
}
return 1;
}
if (*tmp <= '9' && *tmp >= '0') {
*idx = (*idx * 10) + (*tmp - '0');
} else {
return 0;
}
}
}
这个函数的作用就是将字符串数字,通过逐个的获取然后计算出它的整型值,如‘23’=》2*10+3*1(当然代码中没这么简单,我这里是打个比方说这个函数的作用),最后赋值给idx变量。
然后到了最关键的一步,检测idx这个key是否存在于数组ht中了。zend_hash_index_exists(ht,idx);
ZEND_API zend_bool ZEND_FASTCALL zend_hash_index_exists(const HashTable *ht, zend_ulong h)
{
Bucket *p;
IS_CONSISTENT(ht);
if (ht->u.flags & HASH_FLAG_PACKED) {
if (h < ht->nNumUsed) {
if (Z_TYPE(ht->arData[h].val) != IS_UNDEF) {
return 1;
}
}
return 0;
}
p = zend_hash_index_find_bucket(ht, h);
return p ? 1 : 0;
}
这里有一个宏HASH_FLAG_PACKED,为真就代表当前数组的key都是系统生成的,也就是说是按从0到1,2,3等等按序排列的,所以判读键为key的是否存在,直接检查arData数组中第idx个元素是否有定义就行了,这里不涉及什么hash查找,冲突解决等一系列问题。但如果HASH_FLAG_PACKED为假,那么肯定就需要先计算idx的hash值,找到key为idx的数据应该在arData的第几位才行。这就要通过函数zend_hash_index_find_bucket了。
static zend_always_inline Bucket *zend_hash_index_find_bucket(const HashTable *ht, zend_ulong h)
{
uint32_t nIndex;
uint32_t idx;
Bucket *p, *arData;
arData = ht->arData;
nIndex = h | ht->nTableMask;
idx = HT_HASH_EX(arData, nIndex);
while (idx != HT_INVALID_IDX) {
ZEND_ASSERT(idx < HT_IDX_TO_HASH(ht->nTableSize));
p = HT_HASH_TO_BUCKET_EX(arData, idx);
if (p->h == h && !p->key) {
return p;
}
idx = Z_NEXT(p->val);
}
return NULL;
}
这里需要明白一点,数字的哈希值就等于他本身,所以才有不计算h的哈希值,就执行h | ht->nTableMask。
然后处理一下冲突,最后得出key为idx的数据是否存在于数组中。
如果idx确确实实是字符串,那么思路更简单一点,最后通过zen_hash_find_bucket来判断是否存在,与上面zend_hash_index_find_bucket不同的是,函数中要先计算字符串key的哈希值,然后再执行h | ht->nTableMask。
因为篇幅有限,就写到这里了,如果有不正确的地方烦请指正!