Skip to the content.

环境 PHP7.2

背景

一个主任务中有许多子任务,每个子任务都有开始时间、结束时间。我想计算主任务耗费的总时长。代码如下


$arrData = [

    [

        'start' => 1,

        'end' => 10,

    ],

    [

        'start' => 12,

        'end' => 20,

    ],

];

echo end($arrData)['end'] - current($arrData)['start'];

结果竟然是8,不能理解。

然后逐个打印这两个值。


$arrData = [

    [

        'start' => 1,

        'end' => 10,

    ],

    [

        'start' => 12,

        'end' => 20,

    ],

];

echo current($arrData)['start'], PHP_EOL; // 输出为1

echo end($arrData)['end'] , PHP_EOL;      // 输出为20

第一个输出为1,第二个输出为20。没问题啊,等等,如果先打印end,再打印current呢。


$arrData = [

    [

        'start' => 1,

        'end' => 10,

    ],

    [

        'start' => 12,

        'end' => 20,

    ],

];

echo end($arrData)['end'] , PHP_EOL;      // 输出为20

echo current($arrData)['start'], PHP_EOL; // 输出为12

第一个输出20,没问题,第二个输出12,输出12,输出12 …..

翻开源码current函数


ext/standard/array.c

PHP_FUNCTION(current)

{

    HashTable *array;

    zval *entry;

    ZEND_PARSE_PARAMETERS_START(1, 1)

        Z_PARAM_ARRAY_OR_OBJECT_HT(array)

    ZEND_PARSE_PARAMETERS_END();

    // 重点zend_hash_get_current_data()函数, 获取当前指针指向的值

    if ((entry = zend_hash_get_current_data(array)) == NULL) {

        RETURN_FALSE;

    }

    if (Z_TYPE_P(entry) == IS_INDIRECT) {

        entry = Z_INDIRECT_P(entry);

    }

    ZVAL_DEREF(entry);

    ZVAL_COPY(return_value, entry);

}

Zend/zend_hash.h

// 传址,当前的指针

#define zend_hash_get_current_data(ht) \

    zend_hash_get_current_data_ex(ht, &(ht)->nInternalPointer)

Zend/zend_hash.c

// current函数传过来的为ht, &(ht)->nInternalPointer

// 获取ht的偏移量为pos的值

ZEND_API zval* ZEND_FASTCALL zend_hash_get_current_data_ex(HashTable *ht, HashPosition *pos)

{

    uint32_t idx = *pos;

    Bucket *p;

    IS_CONSISTENT(ht);

    if (idx != HT_INVALID_IDX) {

        p = ht->arData + idx;

        return &p->val;

    } else {

        return NULL;

    }

}

翻开源码end函数


PHP_FUNCTION(end)

{

    HashTable *array;

    zval *entry;

    ZEND_PARSE_PARAMETERS_START(1, 1)

        Z_PARAM_ARRAY_OR_OBJECT_HT_EX(array, 0, 1)

    ZEND_PARSE_PARAMETERS_END();

    // 重点将指针移到最后一个位置

    zend_hash_internal_pointer_end(array);

    if (USED_RET()) {

        // 重点zend_hash_get_current_data()函数, 获取当前指针指向的值

        if ((entry = zend_hash_get_current_data(array)) == NULL) {

            RETURN_FALSE;

        }

        if (Z_TYPE_P(entry) == IS_INDIRECT) {

            entry = Z_INDIRECT_P(entry);

        }

        ZVAL_DEREF(entry);

        ZVAL_COPY(return_value, entry);

    }

}

// 重点看下这个zend_hash_internal_pointer_end函数

Zend/zend_hash.h

// 指针传址

#define zend_hash_internal_pointer_end(ht) \

    zend_hash_internal_pointer_end_ex(ht, &(ht)->nInternalPointer)

Zend/zend_hash.c

ZEND_API void ZEND_FASTCALL zend_hash_internal_pointer_end_ex(HashTable *ht, HashPosition *pos)

{

    uint32_t idx;

    IS_CONSISTENT(ht);

    HT_ASSERT(ht, &ht->nInternalPointer != pos || GC_REFCOUNT(ht) == 1);

    idx = ht->nNumUsed;

    // 在这里循环 将当前的指针,指向最后一个

    while (idx > 0) {

        idx--;

        if (Z_TYPE(ht->arData[idx].val) != IS_UNDEF) {

            *pos = idx;

            return;

        }

    }

    *pos = HT_INVALID_IDX;

}

总结

源码注解:https://github.com/gxpisme/read_php/commit/f53beab43abf3549d00252b68e20bfb6ac9cf091

推荐看下PHP5.6的hash table结构。

http://blog.xpisme.com/posts/PHP/2018/04/09/php-hashtable/

推荐看下PHP7.2的hash table结构。

http://blog.xpisme.com/posts/PHP/2018/04/19/php7-hashtable/