PHP源码分析-函数array_merge的”BUG”

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值