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)
相關文章
- count 函式原始碼分析函式原始碼
- [PHP原始碼閱讀]strlen函式PHP原始碼函式
- redux原始碼分析之四:compose函式Redux原始碼函式
- [原始碼分析系列] 不要在迴圈體中使用 array_merge ()原始碼
- 【原始碼】Redis exists命令bug分析原始碼Redis
- [PHP原始碼閱讀]strpos、strstr和stripos、stristr函式PHP原始碼函式
- php-src原始碼zend_startup_builtin_functions函式PHP原始碼UIFunction函式
- 排名前100的PHP函式及分析PHP函式
- 128 PHP合併陣列+與array_merge的區別分析PHP陣列
- js的call函式”原始碼”JS函式原始碼
- webpack 4.0 Tapable 類中的常用鉤子函式原始碼分析Web函式原始碼
- C++(STL原始碼):37---仿函式(函式物件)原始碼剖析C++原始碼函式物件
- Vue 原始碼中的工具函式Vue原始碼函式
- 臨時讀原始碼的函式原始碼函式
- oracle interval日期函式的bug!Oracle函式
- [docker+gdb] 除錯 PHP 原始碼,看 strval 函式 C 實現Docker除錯PHP原始碼函式
- php 函式PHP函式
- php函式PHP函式
- PHP 函式PHP函式
- PHP 手冊中的匿名函式關聯用法分析PHP函式
- bind函式polyfill原始碼解析函式原始碼
- 函式的柯里化與Redux中介軟體及applyMiddleware原始碼分析函式ReduxAPP原始碼
- ClickHouse原始碼筆記5:聚合函式的原始碼再梳理原始碼筆記函式
- [20191002]函式dump的bug.txt函式
- [BUG反饋]除錯模式下函式U()的BUG除錯模式函式
- Zepto 原始碼分析 3 - qsa 實現與工具函式設計原始碼函式
- LiteOS-任務篇-原始碼分析-系統啟動函式原始碼函式
- LiteOS-任務篇-原始碼分析-任務排程函式原始碼函式
- PHP程式碼簡潔之道——函式部分PHP函式
- php操作string的函式PHP函式
- PHP 函式庫 1 - 函式庫的分類PHP函式
- 【原始碼】Scrollsubplot:subplot函式的升級版原始碼函式
- PHP 常用函式PHP函式
- PHP匿名函式PHP函式
- PHP常用函式PHP函式
- PHP函式大全PHP函式
- PHP函式mktimePHP函式
- PHP字串函式PHP字串函式