PHP源码分析-函数array_merge的”BUG”
首先来看段代码.
<?php
$a = [ '2' => 'a' , 'k' => 'g' ];
$b = [ '6' => 'h' , 'd' => 's' ];
$c = array_merge( $a , $b );
$d = array_merge( $b , $a );
var_dump( $c , $d );
运行结果
array(4) {
[0]=>
string(1) "a"
["k"]=>
string(1) "g"
[1]=>
string(1) "h"
["d"]=>
string(1) "s"
}
array(4) {
[0]=>
string(1) "h"
["d"]=>
string(1) "s"
[1]=>
string(1) "a"
["k"]=>
string(1) "g"
}
可以看到a和h的键被重置了而且两个的结果是不一样的.也是就说array_merge是有序的,和我们的一般认知是有出入的,而这个更类似于append的操作.
(这里只考虑数字键的重置问题,不考虑这个函数的其他问题)
为了弄清楚这个问题根本的原因还是得去看源码.
首先找到array_merge的源码实现
PHP_FUNCTION(array_merge)
{
php_array_merge_or_replace_wrapper(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0);
}
static inline void php_array_merge_or_replace_wrapper(INTERNAL_FUNCTION_PARAMETERS, int recursive, int replace) /* {{{ */
{
zval *args = NULL;
zval *arg;
int argc, i;
ZEND_PARSE_PARAMETERS_START(1, -1)
Z_PARAM_VARIADIC('+', args, argc)
ZEND_PARSE_PARAMETERS_END();
if (replace) {
//省略无关的源码
}else{
//必要的参数校验和目的数组初始化
zval *src_entry;
HashTable *src, *dest;
uint32_t count = 0;
for (i = 0; i < argc; i++) {
zval *arg = args + i;
if (Z_TYPE_P(arg) != IS_ARRAY) {
php_error_docref(NULL, E_WARNING, "Expected parameter %d to be an array, %s given", i + 1, zend_zval_type_name(arg));
RETURN_NULL();
}
count += zend_hash_num_elements(Z_ARRVAL_P(arg));
}
arg = args;
src = Z_ARRVAL_P(arg);
/* copy first array */
array_init_size(return_value, count);
dest = Z_ARRVAL_P(return_value);
if (HT_FLAGS(src) & HASH_FLAG_PACKED) {
//省略无关代码
}else{
//完成目的数组初始化并开始copy需要合并的数组
zend_string *string_key;
zend_hash_real_init_mixed(dest);
ZEND_HASH_FOREACH_STR_KEY_VAL(src, string_key, src_entry) {
if (UNEXPECTED(Z_ISREF_P(src_entry) &&
Z_REFCOUNT_P(src_entry) == 1)) {
src_entry = Z_REFVAL_P(src_entry);
}
Z_TRY_ADDREF_P(src_entry);
//这里有区别的了,如果是key是字符串会是一种操作key不是字符串又是另一种操作,从而造成了数字key的重置.具体过程这里就不展开说了,主要是和php的hash表在处理数字键的机制有关.
if (EXPECTED(string_key)) {
zend_hash_append(dest, string_key, src_entry);
} else {
zend_hash_next_index_insert_new(dest, src_entry);//这里
}
} ZEND_HASH_FOREACH_END();
}
if (recursive) {
//省略无关代码
} else {
//重复上边不包含初始化的过程
for (i = 1; i < argc; i++) {
arg = args + i;
php_array_merge(dest, Z_ARRVAL_P(arg));
}
}
}
}
关于ZEND_HASH_FOREACH_STR_KEY_VAL和ZEND_HASH_FOREACH_END两个标记其实就是两个宏.
这两个宏展开如下
#define ZEND_HASH_FOREACH_STR_KEY_VAL(ht, _key, _val) \
ZEND_HASH_FOREACH(ht, 0); \
_key = _p->key; \
_val = _z;
#define ZEND_HASH_FOREACH(_ht, indirect) do { \
HashTable *__ht = (_ht); \
Bucket *_p = __ht->arData; \
Bucket *_end = _p + __ht->nNumUsed; \
for (; _p != _end; _p++) { \
zval *_z = &_p->val; \
if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) { \
_z = Z_INDIRECT_P(_z); \
} \
if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue;
#define ZEND_HASH_FOREACH_END() \
} \
} while (0)