zendhash.c

/*
   +----------------------------------------------------------------------+
   | Zend Engine                                                          |
   +----------------------------------------------------------------------+
   | Copyright (c) 1998-2012 Zend Technologies Ltd. (http://www.zend.com) |
   +----------------------------------------------------------------------+
   | This source file is subject to version 2.00 of the Zend license,     |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.zend.com/license/2_00.txt.                                |
   | If you did not receive a copy of the Zend license and are unable to  |
   | obtain it through the world-wide-web, please send a note to          |
   | license@zend.com so we can mail you a copy immediately.              |
   +----------------------------------------------------------------------+
   | Authors: Andi Gutmans <andi@zend.com>                                |
   |          Zeev Suraski <zeev@zend.com>                                |
   +----------------------------------------------------------------------+
*/

/* $Id$ */

#include "zend.h"
#include "zend_globals.h"

#define CONNECT_TO_BUCKET_DLLIST(element, list_head)        \
    (element)->pNext = (list_head);                            \
    (element)->pLast = NULL;                                \
    if ((element)->pNext) {                                    \
        (element)->pNext->pLast = (element);                \
    }

#define CONNECT_TO_GLOBAL_DLLIST(element, ht)                \
    (element)->pListLast = (ht)->pListTail;                    \
    (ht)->pListTail = (element);                            \
    (element)->pListNext = NULL;                            \
    if ((element)->pListLast != NULL) {                        \
        (element)->pListLast->pListNext = (element);        \
    }                                                        \
    if (!(ht)->pListHead) {                                    \
        (ht)->pListHead = (element);                        \
    }                                                        \
    if ((ht)->pInternalPointer == NULL) {                    \
        (ht)->pInternalPointer = (element);                    \
    }

#if ZEND_DEBUG
#define HT_OK                0
#define HT_IS_DESTROYING    1
#define HT_DESTROYED        2
#define HT_CLEANING            3

static void _zend_is_inconsistent(const HashTable *ht, const char *file, int line)
{
    if (ht->inconsistent==HT_OK) {
        return;
    }
    switch (ht->inconsistent) {
        case HT_IS_DESTROYING:
            zend_output_debug_string(1, "%s(%d) : ht=%p is being destroyed", file, line, ht);
            break;
        case HT_DESTROYED:
            zend_output_debug_string(1, "%s(%d) : ht=%p is already destroyed", file, line, ht);
            break;
        case HT_CLEANING:
            zend_output_debug_string(1, "%s(%d) : ht=%p is being cleaned", file, line, ht);
            break;
        default:
            zend_output_debug_string(1, "%s(%d) : ht=%p is inconsistent", file, line, ht);
            break;
    }
    zend_bailout();
}
#define IS_CONSISTENT(a) _zend_is_inconsistent(a, __FILE__, __LINE__);
#define SET_INCONSISTENT(n) ht->inconsistent = n;
#else
#define IS_CONSISTENT(a)
#define SET_INCONSISTENT(n)
#endif

#define HASH_PROTECT_RECURSION(ht)                                                        \
    if ((ht)->bApplyProtection) {                                                        \
        if ((ht)->nApplyCount++ >= 3) {                                                    \
            zend_error(E_ERROR, "Nesting level too deep - recursive dependency?");        \
        }                                                                                \
    }


#define HASH_UNPROTECT_RECURSION(ht)                                                    \
    if ((ht)->bApplyProtection) {                                                        \
        (ht)->nApplyCount--;                                                            \
    }


#define ZEND_HASH_IF_FULL_DO_RESIZE(ht)                \
    if ((ht)->nNumOfElements > (ht)->nTableSize) {    \
        zend_hash_do_resize(ht);                    \
    }

static int zend_hash_do_resize(HashTable *ht);

ZEND_API ulong zend_hash_func(const char *arKey, uint nKeyLength)
{
    return zend_inline_hash_func(arKey, nKeyLength);
}


#define UPDATE_DATA(ht, p, pData, nDataSize)                                            \
    if (nDataSize == sizeof(void*)) {                                                    \
        if ((p)->pData != &(p)->pDataPtr) {                                                \
            pefree_rel((p)->pData, (ht)->persistent);                                    \
        }                                                                                \
        memcpy(&(p)->pDataPtr, pData, sizeof(void *));                                    \
        (p)->pData = &(p)->pDataPtr;                                                    \
    } else {                                                                            \
        if ((p)->pData == &(p)->pDataPtr) {                                                \
            (p)->pData = (void *) pemalloc_rel(nDataSize, (ht)->persistent);            \
            (p)->pDataPtr=NULL;                                                            \
        } else {                                                                        \
            (p)->pData = (void *) perealloc_rel((p)->pData, nDataSize, (ht)->persistent);    \
            /* (p)->pDataPtr is already NULL so no need to initialize it */                \
        }                                                                                \
        memcpy((p)->pData, pData, nDataSize);                                            \
    }

#define INIT_DATA(ht, p, pData, nDataSize);                                \
    if (nDataSize == sizeof(void*)) {                                    \
        memcpy(&(p)->pDataPtr, pData, sizeof(void *));                    \
        (p)->pData = &(p)->pDataPtr;                                    \
    } else {                                                            \
        (p)->pData = (void *) pemalloc_rel(nDataSize, (ht)->persistent);\
        if (!(p)->pData) {                                                \
            pefree_rel(p, (ht)->persistent);                            \
            return FAILURE;                                                \
        }                                                                \
        memcpy((p)->pData, pData, nDataSize);                            \
        (p)->pDataPtr=NULL;                                                \
    }

#define CHECK_INIT(ht) do {                                                \
    if (UNEXPECTED((ht)->nTableMask == 0)) {                                \
        (ht)->arBuckets = (Bucket **) pecalloc((ht)->nTableSize, sizeof(Bucket *), (ht)->persistent);    \
        (ht)->nTableMask = (ht)->nTableSize - 1;                        \
    }                                                                    \
} while (0)
 
static const Bucket *uninitialized_bucket = NULL;

ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
{
    uint i = 3;

    SET_INCONSISTENT(HT_OK);

    if (nSize >= 0x80000000) {
        /* prevent overflow */
        ht->nTableSize = 0x80000000;
    } else {
        while ((1U << i) < nSize) {
            i++;
        }
        ht->nTableSize = 1 << i;
    }

    ht->nTableMask = 0;    /* 0 means that ht->arBuckets is uninitialized */
    ht->pDestructor = pDestructor;
    ht->arBuckets = (Bucket**)&uninitialized_bucket;
    ht->pListHead = NULL;
    ht->pListTail = NULL;
    ht->nNumOfElements = 0;
    ht->nNextFreeElement = 0;
    ht->pInternalPointer = NULL;
    ht->persistent = persistent;
    ht->nApplyCount = 0;
    ht->bApplyProtection = 1;
    return SUCCESS;
}


ZEND_API int _zend_hash_init_ex(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent, zend_bool bApplyProtection ZEND_FILE_LINE_DC)
{
    int retval = _zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent ZEND_FILE_LINE_CC);

    ht->bApplyProtection = bApplyProtection;
    return retval;
}


ZEND_API void zend_hash_set_apply_protection(HashTable *ht, zend_bool bApplyProtection)
{
    ht->bApplyProtection = bApplyProtection;
}



ZEND_API int _zend_hash_add_or_update(HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)
{
    ulong h;
    uint nIndex;
    Bucket *p;
#ifdef ZEND_SIGNALS
    TSRMLS_FETCH();
#endif

    IS_CONSISTENT(ht);

    if (nKeyLength <= 0) {
#if ZEND_DEBUG
        ZEND_PUTS("zend_hash_update: Can't put in empty key\n");
#endif
        return FAILURE;
    }

    CHECK_INIT(ht);

    h = zend_inline_hash_func(arKey, nKeyLength);
    nIndex = h & ht->nTableMask;

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if (p->arKey == arKey ||
            ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) {
                if (flag & HASH_ADD) {
                    return FAILURE;
                }
                HANDLE_BLOCK_INTERRUPTIONS();
#if ZEND_DEBUG
                if (p->pData == pData) {
                    ZEND_PUTS("Fatal error in zend_hash_update: p->pData == pData\n");
                    HANDLE_UNBLOCK_INTERRUPTIONS();
                    return FAILURE;
                }
#endif
                if (ht->pDestructor) {
                    ht->pDestructor(p->pData);
                }
                UPDATE_DATA(ht, p, pData, nDataSize);
                if (pDest) {
                    *pDest = p->pData;
                }
                HANDLE_UNBLOCK_INTERRUPTIONS();
                return SUCCESS;
        }
        p = p->pNext;
    }
    
    if (IS_INTERNED(arKey)) {
        p = (Bucket *) pemalloc(sizeof(Bucket), ht->persistent);
        if (!p) {
            return FAILURE;
        }
        p->arKey = arKey;
    } else {
        p = (Bucket *) pemalloc(sizeof(Bucket) + nKeyLength, ht->persistent);
        if (!p) {
            return FAILURE;
        }
        p->arKey = (const char*)(p + 1);
        memcpy((char*)p->arKey, arKey, nKeyLength);
    }
    p->nKeyLength = nKeyLength;
    INIT_DATA(ht, p, pData, nDataSize);
    p->h = h;
    CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);
    if (pDest) {
        *pDest = p->pData;
    }

    HANDLE_BLOCK_INTERRUPTIONS();
    CONNECT_TO_GLOBAL_DLLIST(p, ht);
    ht->arBuckets[nIndex] = p;
    HANDLE_UNBLOCK_INTERRUPTIONS();

    ht->nNumOfElements++;
    ZEND_HASH_IF_FULL_DO_RESIZE(ht);        /* If the Hash table is full, resize it */
    return SUCCESS;
}

ZEND_API int _zend_hash_quick_add_or_update(HashTable *ht, const char *arKey, uint nKeyLength, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)
{
    uint nIndex;
    Bucket *p;
#ifdef ZEND_SIGNALS
    TSRMLS_FETCH();
#endif

    IS_CONSISTENT(ht);

    if (nKeyLength == 0) {
        return zend_hash_index_update(ht, h, pData, nDataSize, pDest);
    }

    CHECK_INIT(ht);
    nIndex = h & ht->nTableMask;
    
    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if (p->arKey == arKey ||
            ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) {
                if (flag & HASH_ADD) {
                    return FAILURE;
                }
                HANDLE_BLOCK_INTERRUPTIONS();
#if ZEND_DEBUG
                if (p->pData == pData) {
                    ZEND_PUTS("Fatal error in zend_hash_update: p->pData == pData\n");
                    HANDLE_UNBLOCK_INTERRUPTIONS();
                    return FAILURE;
                }
#endif
                if (ht->pDestructor) {
                    ht->pDestructor(p->pData);
                }
                UPDATE_DATA(ht, p, pData, nDataSize);
                if (pDest) {
                    *pDest = p->pData;
                }
                HANDLE_UNBLOCK_INTERRUPTIONS();
                return SUCCESS;
        }
        p = p->pNext;
    }
    
    if (IS_INTERNED(arKey)) {
        p = (Bucket *) pemalloc(sizeof(Bucket), ht->persistent);
        if (!p) {
            return FAILURE;
        }
        p->arKey = arKey;
    } else {
        p = (Bucket *) pemalloc(sizeof(Bucket) + nKeyLength, ht->persistent);
        if (!p) {
            return FAILURE;
        }
        p->arKey = (const char*)(p + 1);
        memcpy((char*)p->arKey, arKey, nKeyLength);
    }

    p->nKeyLength = nKeyLength;
    INIT_DATA(ht, p, pData, nDataSize);
    p->h = h;
    
    CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);

    if (pDest) {
        *pDest = p->pData;
    }

    HANDLE_BLOCK_INTERRUPTIONS();
    ht->arBuckets[nIndex] = p;
    CONNECT_TO_GLOBAL_DLLIST(p, ht);
    HANDLE_UNBLOCK_INTERRUPTIONS();

    ht->nNumOfElements++;
    ZEND_HASH_IF_FULL_DO_RESIZE(ht);        /* If the Hash table is full, resize it */
    return SUCCESS;
}


ZEND_API int zend_hash_add_empty_element(HashTable *ht, const char *arKey, uint nKeyLength)
{
    void *dummy = (void *) 1;

    return zend_hash_add(ht, arKey, nKeyLength, &dummy, sizeof(void *), NULL);
}


ZEND_API int _zend_hash_index_update_or_next_insert(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)
{
    uint nIndex;
    Bucket *p;
#ifdef ZEND_SIGNALS
    TSRMLS_FETCH();
#endif

    IS_CONSISTENT(ht);
    CHECK_INIT(ht);

    if (flag & HASH_NEXT_INSERT) {
        h = ht->nNextFreeElement;
    }
    nIndex = h & ht->nTableMask;

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if ((p->nKeyLength == 0) && (p->h == h)) {
            if (flag & HASH_NEXT_INSERT || flag & HASH_ADD) {
                return FAILURE;
            }
            HANDLE_BLOCK_INTERRUPTIONS();
#if ZEND_DEBUG
            if (p->pData == pData) {
                ZEND_PUTS("Fatal error in zend_hash_index_update: p->pData == pData\n");
                HANDLE_UNBLOCK_INTERRUPTIONS();
                return FAILURE;
            }
#endif
            if (ht->pDestructor) {
                ht->pDestructor(p->pData);
            }
            UPDATE_DATA(ht, p, pData, nDataSize);
            HANDLE_UNBLOCK_INTERRUPTIONS();
            if ((long)h >= (long)ht->nNextFreeElement) {
                ht->nNextFreeElement = h < LONG_MAX ? h + 1 : LONG_MAX;
            }
            if (pDest) {
                *pDest = p->pData;
            }
            return SUCCESS;
        }
        p = p->pNext;
    }
    p = (Bucket *) pemalloc_rel(sizeof(Bucket), ht->persistent);
    if (!p) {
        return FAILURE;
    }
    p->arKey = NULL;
    p->nKeyLength = 0; /* Numeric indices are marked by making the nKeyLength == 0 */
    p->h = h;
    INIT_DATA(ht, p, pData, nDataSize);
    if (pDest) {
        *pDest = p->pData;
    }

    CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);

    HANDLE_BLOCK_INTERRUPTIONS();
    ht->arBuckets[nIndex] = p;
    CONNECT_TO_GLOBAL_DLLIST(p, ht);
    HANDLE_UNBLOCK_INTERRUPTIONS();

    if ((long)h >= (long)ht->nNextFreeElement) {
        ht->nNextFreeElement = h < LONG_MAX ? h + 1 : LONG_MAX;
    }
    ht->nNumOfElements++;
    ZEND_HASH_IF_FULL_DO_RESIZE(ht);
    return SUCCESS;
}


static int zend_hash_do_resize(HashTable *ht)
{
    Bucket **t;
#ifdef ZEND_SIGNALS
    TSRMLS_FETCH();
#endif

    IS_CONSISTENT(ht);

    if ((ht->nTableSize << 1) > 0) {    /* Let's double the table size */
        t = (Bucket **) perealloc_recoverable(ht->arBuckets, (ht->nTableSize << 1) * sizeof(Bucket *), ht->persistent);
        if (t) {
            HANDLE_BLOCK_INTERRUPTIONS();
            ht->arBuckets = t;
            ht->nTableSize = (ht->nTableSize << 1);
            ht->nTableMask = ht->nTableSize - 1;
            zend_hash_rehash(ht);
            HANDLE_UNBLOCK_INTERRUPTIONS();
            return SUCCESS;
        }
        return FAILURE;
    }
    return SUCCESS;
}

ZEND_API int zend_hash_rehash(HashTable *ht)
{
    Bucket *p;
    uint nIndex;

    IS_CONSISTENT(ht);
    if (UNEXPECTED(ht->nNumOfElements == 0)) {
        return SUCCESS;
    }

    memset(ht->arBuckets, 0, ht->nTableSize * sizeof(Bucket *));
    p = ht->pListHead;
    while (p != NULL) {
        nIndex = p->h & ht->nTableMask;
        CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);
        ht->arBuckets[nIndex] = p;
        p = p->pListNext;
    }
    return SUCCESS;
}

ZEND_API int zend_hash_del_key_or_index(HashTable *ht, const char *arKey, uint nKeyLength, ulong h, int flag)
{
    uint nIndex;
    Bucket *p;
#ifdef ZEND_SIGNALS
    TSRMLS_FETCH();
#endif

    IS_CONSISTENT(ht);

    if (flag == HASH_DEL_KEY) {
        h = zend_inline_hash_func(arKey, nKeyLength);
    }
    nIndex = h & ht->nTableMask;

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if ((p->h == h)
             && (p->nKeyLength == nKeyLength)
             && ((p->nKeyLength == 0) /* Numeric index (short circuits the memcmp() check) */
                 || !memcmp(p->arKey, arKey, nKeyLength))) { /* String index */
            HANDLE_BLOCK_INTERRUPTIONS();
            if (p == ht->arBuckets[nIndex]) {
                ht->arBuckets[nIndex] = p->pNext;
            } else {
                p->pLast->pNext = p->pNext;
            }
            if (p->pNext) {
                p->pNext->pLast = p->pLast;
            }
            if (p->pListLast != NULL) {
                p->pListLast->pListNext = p->pListNext;
            } else {
                /* Deleting the head of the list */
                ht->pListHead = p->pListNext;
            }
            if (p->pListNext != NULL) {
                p->pListNext->pListLast = p->pListLast;
            } else {
                ht->pListTail = p->pListLast;
            }
            if (ht->pInternalPointer == p) {
                ht->pInternalPointer = p->pListNext;
            }
            if (ht->pDestructor) {
                ht->pDestructor(p->pData);
            }
            if (p->pData != &p->pDataPtr) {
                pefree(p->pData, ht->persistent);
            }
            pefree(p, ht->persistent);
            HANDLE_UNBLOCK_INTERRUPTIONS();
            ht->nNumOfElements--;
            return SUCCESS;
        }
        p = p->pNext;
    }
    return FAILURE;
}


ZEND_API void zend_hash_destroy(HashTable *ht)
{
    Bucket *p, *q;

    IS_CONSISTENT(ht);

    SET_INCONSISTENT(HT_IS_DESTROYING);

    p = ht->pListHead;
    while (p != NULL) {
        q = p;
        p = p->pListNext;
        if (ht->pDestructor) {
            ht->pDestructor(q->pData);
        }
        if (q->pData != &q->pDataPtr) {
            pefree(q->pData, ht->persistent);
        }
        pefree(q, ht->persistent);
    }
    if (ht->nTableMask) {
        pefree(ht->arBuckets, ht->persistent);
    }

    SET_INCONSISTENT(HT_DESTROYED);
}


ZEND_API void zend_hash_clean(HashTable *ht)
{
    Bucket *p, *q;

    IS_CONSISTENT(ht);

    p = ht->pListHead;

    if (ht->nTableMask) {
        memset(ht->arBuckets, 0, ht->nTableSize*sizeof(Bucket *));
    }
    ht->pListHead = NULL;
    ht->pListTail = NULL;
    ht->nNumOfElements = 0;
    ht->nNextFreeElement = 0;
    ht->pInternalPointer = NULL;

    while (p != NULL) {
        q = p;
        p = p->pListNext;
        if (ht->pDestructor) {
            ht->pDestructor(q->pData);
        }
        if (q->pData != &q->pDataPtr) {
            pefree(q->pData, ht->persistent);
        }
        pefree(q, ht->persistent);
    }
}

/* This function is used by the various apply() functions.
 * It deletes the passed bucket, and returns the address of the
 * next bucket.  The hash *may* be altered during that time, the
 * returned value will still be valid.
 */
static Bucket *zend_hash_apply_deleter(HashTable *ht, Bucket *p)
{
    Bucket *retval;
#ifdef ZEND_SIGNALS
    TSRMLS_FETCH();
#endif

    HANDLE_BLOCK_INTERRUPTIONS();
    if (p->pLast) {
        p->pLast->pNext = p->pNext;
    } else {
        uint nIndex;

        nIndex = p->h & ht->nTableMask;
        ht->arBuckets[nIndex] = p->pNext;
    }
    if (p->pNext) {
        p->pNext->pLast = p->pLast;
    } else {
        /* Nothing to do as this list doesn't have a tail */
    }

    if (p->pListLast != NULL) {
        p->pListLast->pListNext = p->pListNext;
    } else {
        /* Deleting the head of the list */
        ht->pListHead = p->pListNext;
    }
    if (p->pListNext != NULL) {
        p->pListNext->pListLast = p->pListLast;
    } else {
        ht->pListTail = p->pListLast;
    }
    if (ht->pInternalPointer == p) {
        ht->pInternalPointer = p->pListNext;
    }
    ht->nNumOfElements--;
    HANDLE_UNBLOCK_INTERRUPTIONS();

    if (ht->pDestructor) {
        ht->pDestructor(p->pData);
    }
    if (p->pData != &p->pDataPtr) {
        pefree(p->pData, ht->persistent);
    }
    retval = p->pListNext;
    pefree(p, ht->persistent);

    return retval;
}


ZEND_API void zend_hash_graceful_destroy(HashTable *ht)
{
    Bucket *p;

    IS_CONSISTENT(ht);

    p = ht->pListHead;
    while (p != NULL) {
        p = zend_hash_apply_deleter(ht, p);
    }
    if (ht->nTableMask) {
        pefree(ht->arBuckets, ht->persistent);
    }

    SET_INCONSISTENT(HT_DESTROYED);
}

ZEND_API void zend_hash_graceful_reverse_destroy(HashTable *ht)
{
    Bucket *p;

    IS_CONSISTENT(ht);

    p = ht->pListTail;
    while (p != NULL) {
        zend_hash_apply_deleter(ht, p);
        p = ht->pListTail;
    }

    if (ht->nTableMask) {
        pefree(ht->arBuckets, ht->persistent);
    }

    SET_INCONSISTENT(HT_DESTROYED);
}

/* This is used to recurse elements and selectively delete certain entries
 * from a hashtable. apply_func() receives the data and decides if the entry
 * should be deleted or recursion should be stopped. The following three
 * return codes are possible:
 * ZEND_HASH_APPLY_KEEP   - continue
 * ZEND_HASH_APPLY_STOP   - stop iteration
 * ZEND_HASH_APPLY_REMOVE - delete the element, combineable with the former
 */

ZEND_API void zend_hash_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC)
{
    Bucket *p;

    IS_CONSISTENT(ht);

    HASH_PROTECT_RECURSION(ht);
    p = ht->pListHead;
    while (p != NULL) {
        int result = apply_func(p->pData TSRMLS_CC);
        
        if (result & ZEND_HASH_APPLY_REMOVE) {
            p = zend_hash_apply_deleter(ht, p);
        } else {
            p = p->pListNext;
        }
        if (result & ZEND_HASH_APPLY_STOP) {
            break;
        }
    }
    HASH_UNPROTECT_RECURSION(ht);
}


ZEND_API void zend_hash_apply_with_argument(HashTable *ht, apply_func_arg_t apply_func, void *argument TSRMLS_DC)
{
    Bucket *p;

    IS_CONSISTENT(ht);

    HASH_PROTECT_RECURSION(ht);
    p = ht->pListHead;
    while (p != NULL) {
        int result = apply_func(p->pData, argument TSRMLS_CC);
        
        if (result & ZEND_HASH_APPLY_REMOVE) {
            p = zend_hash_apply_deleter(ht, p);
        } else {
            p = p->pListNext;
        }
        if (result & ZEND_HASH_APPLY_STOP) {
            break;
        }
    }
    HASH_UNPROTECT_RECURSION(ht);
}


ZEND_API void zend_hash_apply_with_arguments(HashTable *ht TSRMLS_DC, apply_func_args_t apply_func, int num_args, ...)
{
    Bucket *p;
    va_list args;
    zend_hash_key hash_key;

    IS_CONSISTENT(ht);

    HASH_PROTECT_RECURSION(ht);

    p = ht->pListHead;
    while (p != NULL) {
        int result;
        va_start(args, num_args);
        hash_key.arKey = p->arKey;
        hash_key.nKeyLength = p->nKeyLength;
        hash_key.h = p->h;
        result = apply_func(p->pData TSRMLS_CC, num_args, args, &hash_key);

        if (result & ZEND_HASH_APPLY_REMOVE) {
            p = zend_hash_apply_deleter(ht, p);
        } else {
            p = p->pListNext;
        }
        if (result & ZEND_HASH_APPLY_STOP) {
            va_end(args);
            break;
        }
        va_end(args);
    }

    HASH_UNPROTECT_RECURSION(ht);
}


ZEND_API void zend_hash_reverse_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC)
{
    Bucket *p, *q;

    IS_CONSISTENT(ht);

    HASH_PROTECT_RECURSION(ht);
    p = ht->pListTail;
    while (p != NULL) {
        int result = apply_func(p->pData TSRMLS_CC);

        q = p;
        p = p->pListLast;
        if (result & ZEND_HASH_APPLY_REMOVE) {
            zend_hash_apply_deleter(ht, q);
        }
        if (result & ZEND_HASH_APPLY_STOP) {
            break;
        }
    }
    HASH_UNPROTECT_RECURSION(ht);
}


ZEND_API void zend_hash_copy(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size)
{
    Bucket *p;
    void *new_entry;
    zend_bool setTargetPointer;

    IS_CONSISTENT(source);
    IS_CONSISTENT(target);

    setTargetPointer = !target->pInternalPointer;
    p = source->pListHead;
    while (p) {
        if (setTargetPointer && source->pInternalPointer == p) {
            target->pInternalPointer = NULL;
        }
        if (p->nKeyLength) {
            zend_hash_quick_update(target, p->arKey, p->nKeyLength, p->h, p->pData, size, &new_entry);
        } else {
            zend_hash_index_update(target, p->h, p->pData, size, &new_entry);
        }
        if (pCopyConstructor) {
            pCopyConstructor(new_entry);
        }
        p = p->pListNext;
    }
    if (!target->pInternalPointer) {
        target->pInternalPointer = target->pListHead;
    }
}


ZEND_API void _zend_hash_merge(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size, int overwrite ZEND_FILE_LINE_DC)
{
    Bucket *p;
    void *t;
    int mode = (overwrite?HASH_UPDATE:HASH_ADD);

    IS_CONSISTENT(source);
    IS_CONSISTENT(target);

    p = source->pListHead;
    while (p) {
        if (p->nKeyLength>0) {
            if (_zend_hash_quick_add_or_update(target, p->arKey, p->nKeyLength, p->h, p->pData, size, &t, mode ZEND_FILE_LINE_RELAY_CC)==SUCCESS && pCopyConstructor) {
                pCopyConstructor(t);
            }
        } else {
            if ((mode==HASH_UPDATE || !zend_hash_index_exists(target, p->h)) && zend_hash_index_update(target, p->h, p->pData, size, &t)==SUCCESS && pCopyConstructor) {
                pCopyConstructor(t);
            }
        }
        p = p->pListNext;
    }
    target->pInternalPointer = target->pListHead;
}


static zend_bool zend_hash_replace_checker_wrapper(HashTable *target, void *source_data, Bucket *p, void *pParam, merge_checker_func_t merge_checker_func)
{
    zend_hash_key hash_key;

    hash_key.arKey = p->arKey;
    hash_key.nKeyLength = p->nKeyLength;
    hash_key.h = p->h;
    return merge_checker_func(target, source_data, &hash_key, pParam);
}


ZEND_API void zend_hash_merge_ex(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, uint size, merge_checker_func_t pMergeSource, void *pParam)
{
    Bucket *p;
    void *t;

    IS_CONSISTENT(source);
    IS_CONSISTENT(target);

    p = source->pListHead;
    while (p) {
        if (zend_hash_replace_checker_wrapper(target, p->pData, p, pParam, pMergeSource)) {
            if (zend_hash_quick_update(target, p->arKey, p->nKeyLength, p->h, p->pData, size, &t)==SUCCESS && pCopyConstructor) {
                pCopyConstructor(t);
            }
        }
        p = p->pListNext;
    }
    target->pInternalPointer = target->pListHead;
}


ZEND_API ulong zend_get_hash_value(const char *arKey, uint nKeyLength)
{
    return zend_inline_hash_func(arKey, nKeyLength);
}


/* Returns SUCCESS if found and FAILURE if not. The pointer to the
 * data is returned in pData. The reason is that there's no reason
 * someone using the hash table might not want to have NULL data
 */
ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData)
{
    ulong h;
    uint nIndex;
    Bucket *p;

    IS_CONSISTENT(ht);

    h = zend_inline_hash_func(arKey, nKeyLength);
    nIndex = h & ht->nTableMask;

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if (p->arKey == arKey ||
            ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) {
                *pData = p->pData;
                return SUCCESS;
        }
        p = p->pNext;
    }
    return FAILURE;
}


ZEND_API int zend_hash_quick_find(const HashTable *ht, const char *arKey, uint nKeyLength, ulong h, void **pData)
{
    uint nIndex;
    Bucket *p;

    if (nKeyLength==0) {
        return zend_hash_index_find(ht, h, pData);
    }

    IS_CONSISTENT(ht);

    nIndex = h & ht->nTableMask;

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if (p->arKey == arKey ||
            ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) {
                *pData = p->pData;
                return SUCCESS;
        }
        p = p->pNext;
    }
    return FAILURE;
}


ZEND_API int zend_hash_exists(const HashTable *ht, const char *arKey, uint nKeyLength)
{
    ulong h;
    uint nIndex;
    Bucket *p;

    IS_CONSISTENT(ht);

    h = zend_inline_hash_func(arKey, nKeyLength);
    nIndex = h & ht->nTableMask;

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if (p->arKey == arKey ||
            ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) {
                return 1;
        }
        p = p->pNext;
    }
    return 0;
}


ZEND_API int zend_hash_quick_exists(const HashTable *ht, const char *arKey, uint nKeyLength, ulong h)
{
    uint nIndex;
    Bucket *p;

    if (nKeyLength==0) {
        return zend_hash_index_exists(ht, h);
    }

    IS_CONSISTENT(ht);

    nIndex = h & ht->nTableMask;

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if (p->arKey == arKey ||
            ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) {
                return 1;
        }
        p = p->pNext;
    }
    return 0;

}


ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData)
{
    uint nIndex;
    Bucket *p;

    IS_CONSISTENT(ht);

    nIndex = h & ht->nTableMask;

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if ((p->h == h) && (p->nKeyLength == 0)) {
            *pData = p->pData;
            return SUCCESS;
        }
        p = p->pNext;
    }
    return FAILURE;
}


ZEND_API int zend_hash_index_exists(const HashTable *ht, ulong h)
{
    uint nIndex;
    Bucket *p;

    IS_CONSISTENT(ht);

    nIndex = h & ht->nTableMask;

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
        if ((p->h == h) && (p->nKeyLength == 0)) {
            return 1;
        }
        p = p->pNext;
    }
    return 0;
}


ZEND_API int zend_hash_num_elements(const HashTable *ht)
{
    IS_CONSISTENT(ht);

    return ht->nNumOfElements;
}


ZEND_API int zend_hash_get_pointer(const HashTable *ht, HashPointer *ptr)
{
    ptr->pos = ht->pInternalPointer;
    if (ht->pInternalPointer) {
        ptr->h = ht->pInternalPointer->h;
        return 1;
    } else {
        ptr->h = 0;
        return 0;
    }
}

ZEND_API int zend_hash_set_pointer(HashTable *ht, const HashPointer *ptr)
{
    if (ptr->pos == NULL) {
        ht->pInternalPointer = NULL;
    } else if (ht->pInternalPointer != ptr->pos) {
        Bucket *p;

        IS_CONSISTENT(ht);
        p = ht->arBuckets[ptr->h & ht->nTableMask];
        while (p != NULL) {
            if (p == ptr->pos) {
                ht->pInternalPointer = p;
                return 1;
            }
            p = p->pNext;
        }
        return 0;
    }
    return 1;
}

ZEND_API void zend_hash_internal_pointer_reset_ex(HashTable *ht, HashPosition *pos)
{
    IS_CONSISTENT(ht);

    if (pos)
        *pos = ht->pListHead;
    else
        ht->pInternalPointer = ht->pListHead;
}


/* This function will be extremely optimized by remembering
 * the end of the list
 */
ZEND_API void zend_hash_internal_pointer_end_ex(HashTable *ht, HashPosition *pos)
{
    IS_CONSISTENT(ht);

    if (pos)
        *pos = ht->pListTail;
    else
        ht->pInternalPointer = ht->pListTail;
}


ZEND_API int zend_hash_move_forward_ex(HashTable *ht, HashPosition *pos)
{
    HashPosition *current = pos ? pos : &ht->pInternalPointer;

    IS_CONSISTENT(ht);

    if (*current) {
        *current = (*current)->pListNext;
        return SUCCESS;
    } else
        return FAILURE;
}

ZEND_API int zend_hash_move_backwards_ex(HashTable *ht, HashPosition *pos)
{
    HashPosition *current = pos ? pos : &ht->pInternalPointer;

    IS_CONSISTENT(ht);

    if (*current) {
        *current = (*current)->pListLast;
        return SUCCESS;
    } else
        return FAILURE;
}


/* This function should be made binary safe  */
ZEND_API int zend_hash_get_current_key_ex(const HashTable *ht, char **str_index, uint *str_length, ulong *num_index, zend_bool duplicate, HashPosition *pos)
{
    Bucket *p;

    p = pos ? (*pos) : ht->pInternalPointer;

    IS_CONSISTENT(ht);

    if (p) {
        if (p->nKeyLength) {
            if (duplicate) {
                *str_index = estrndup(p->arKey, p->nKeyLength - 1);
            } else {
                *str_index = (char*)p->arKey;
            }
            if (str_length) {
                *str_length = p->nKeyLength;
            }
            return HASH_KEY_IS_STRING;
        } else {
            *num_index = p->h;
            return HASH_KEY_IS_LONG;
        }
    }
    return HASH_KEY_NON_EXISTANT;
}


ZEND_API int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos)
{
    Bucket *p;

    p = pos ? (*pos) : ht->pInternalPointer;

    IS_CONSISTENT(ht);

    if (p) {
        if (p->nKeyLength) {
            return HASH_KEY_IS_STRING;
        } else {
            return HASH_KEY_IS_LONG;
        }
    }
    return HASH_KEY_NON_EXISTANT;
}


ZEND_API int zend_hash_get_current_data_ex(HashTable *ht, void **pData, HashPosition *pos)
{
    Bucket *p;

    p = pos ? (*pos) : ht->pInternalPointer;

    IS_CONSISTENT(ht);

    if (p) {
        *pData = p->pData;
        return SUCCESS;
    } else {
        return FAILURE;
    }
}

/* This function changes key of current element without changing elements'
 * order. If element with target key already exists, it will be deleted first.
 */
ZEND_API int zend_hash_update_current_key_ex(HashTable *ht, int key_type, const char *str_index, uint str_length, ulong num_index, int mode, HashPosition *pos)
{
    Bucket *p, *q;
    ulong h;
#ifdef ZEND_SIGNALS
    TSRMLS_FETCH();
#endif

    p = pos ? (*pos) : ht->pInternalPointer;

    IS_CONSISTENT(ht);

    if (p) {
        if (key_type == HASH_KEY_IS_LONG) {
            str_length = 0;
            if (!p->nKeyLength && p->h == num_index) {
                return SUCCESS;
            }

            q = ht->arBuckets[num_index & ht->nTableMask];
            while (q != NULL) {
                if (!q->nKeyLength && q->h == num_index) {
                    break;
                }
                q = q->pNext;
            }
        } else if (key_type == HASH_KEY_IS_STRING) {
            if (IS_INTERNED(str_index)) {
                h = INTERNED_HASH(str_index);
            } else {
                h = zend_inline_hash_func(str_index, str_length);
            }

            if (p->arKey == str_index ||
                (p->nKeyLength == str_length &&
                 p->h == h &&
                 memcmp(p->arKey, str_index, str_length) == 0)) {
                return SUCCESS;
            }

            q = ht->arBuckets[h & ht->nTableMask];

            while (q != NULL) {
                if (q->arKey == str_index ||
                    (q->h == h && q->nKeyLength == str_length &&
                     memcmp(q->arKey, str_index, str_length) == 0)) {
                    break;
                }
                q = q->pNext;
            }
        } else {
            return FAILURE;
        }

        HANDLE_BLOCK_INTERRUPTIONS();

        if (q) {
            if (mode != HASH_UPDATE_KEY_ANYWAY) {
                Bucket *r = p->pListLast;
                int found = HASH_UPDATE_KEY_IF_BEFORE;

                while (r) {
                    if (r == q) {
                        found = HASH_UPDATE_KEY_IF_AFTER;
                        break;
                    }
                    r = r->pListLast;
                }
                if (mode & found) {
                    /* delete current bucket */
                    if (p == ht->arBuckets[p->h & ht->nTableMask]) {
                        ht->arBuckets[p->h & ht->nTableMask] = p->pNext;
                    } else {
                        p->pLast->pNext = p->pNext;
                    }
                    if (p->pNext) {
                        p->pNext->pLast = p->pLast;
                    }
                    if (p->pListLast != NULL) {
                        p->pListLast->pListNext = p->pListNext;
                    } else {
                        /* Deleting the head of the list */
                        ht->pListHead = p->pListNext;
                    }
                    if (p->pListNext != NULL) {
                        p->pListNext->pListLast = p->pListLast;
                    } else {
                        ht->pListTail = p->pListLast;
                    }
                    if (ht->pInternalPointer == p) {
                        ht->pInternalPointer = p->pListNext;
                    }
                    if (ht->pDestructor) {
                        ht->pDestructor(p->pData);
                    }
                    if (p->pData != &p->pDataPtr) {
                        pefree(p->pData, ht->persistent);
                    }
                    pefree(p, ht->persistent);
                    ht->nNumOfElements--;
                    HANDLE_UNBLOCK_INTERRUPTIONS();
                    return FAILURE;
                }
            }
            /* delete another bucket with the same key */
            if (q == ht->arBuckets[q->h & ht->nTableMask]) {
                ht->arBuckets[q->h & ht->nTableMask] = q->pNext;
            } else {
                q->pLast->pNext = q->pNext;
            }
            if (q->pNext) {
                q->pNext->pLast = q->pLast;
            }
            if (q->pListLast != NULL) {
                q->pListLast->pListNext = q->pListNext;
            } else {
                /* Deleting the head of the list */
                ht->pListHead = q->pListNext;
            }
            if (q->pListNext != NULL) {
                q->pListNext->pListLast = q->pListLast;
            } else {
                ht->pListTail = q->pListLast;
            }
            if (ht->pInternalPointer == q) {
                ht->pInternalPointer = q->pListNext;
            }
            if (ht->pDestructor) {
                ht->pDestructor(q->pData);
            }
            if (q->pData != &q->pDataPtr) {
                pefree(q->pData, ht->persistent);
            }
            pefree(q, ht->persistent);
            ht->nNumOfElements--;
        }

        if (p->pNext) {
            p->pNext->pLast = p->pLast;
        }
        if (p->pLast) {
            p->pLast->pNext = p->pNext;
        } else {
            ht->arBuckets[p->h & ht->nTableMask] = p->pNext;
        }

        if ((IS_INTERNED(p->arKey) != IS_INTERNED(str_index)) ||
            (!IS_INTERNED(p->arKey) && p->nKeyLength != str_length)) {
            Bucket *q;

            if (IS_INTERNED(str_index)) {
                q = (Bucket *) pemalloc(sizeof(Bucket), ht->persistent);
            } else {
                q = (Bucket *) pemalloc(sizeof(Bucket) + str_length, ht->persistent);
            }

            q->nKeyLength = str_length;
            if (p->pData == &p->pDataPtr) {
                q->pData = &q->pDataPtr;
            } else {
                q->pData = p->pData;
            }
            q->pDataPtr = p->pDataPtr;
            q->pListNext = p->pListNext;
            q->pListLast = p->pListLast;
            if (q->pListNext) {
                p->pListNext->pListLast = q;
            } else {
                ht->pListTail = q;
            }
            if (q->pListLast) {
                p->pListLast->pListNext = q;
            } else {
                ht->pListHead = q;
            }
            if (ht->pInternalPointer == p) {
                ht->pInternalPointer = q;
            }
            if (pos) {
                *pos = q;
            }
            pefree(p, ht->persistent);
            p = q;
        }

        if (key_type == HASH_KEY_IS_LONG) {
            p->h = num_index;
        } else {
            p->h = h;
            p->nKeyLength = str_length;
            if (IS_INTERNED(str_index)) {
                p->arKey = str_index;
            } else {
                p->arKey = (const char*)(p+1);
                memcpy((char*)p->arKey, str_index, str_length);
            }
        }

        CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[p->h & ht->nTableMask]);
        ht->arBuckets[p->h & ht->nTableMask] = p;
        HANDLE_UNBLOCK_INTERRUPTIONS();

        return SUCCESS;
    } else {
        return FAILURE;
    }
}

ZEND_API int zend_hash_sort(HashTable *ht, sort_func_t sort_func,
                            compare_func_t compar, int renumber TSRMLS_DC)
{
    Bucket **arTmp;
    Bucket *p;
    int i, j;

    IS_CONSISTENT(ht);

    if (!(ht->nNumOfElements>1) && !(renumber && ht->nNumOfElements>0)) { /* Doesn't require sorting */
        return SUCCESS;
    }
    arTmp = (Bucket **) pemalloc(ht->nNumOfElements * sizeof(Bucket *), ht->persistent);
    if (!arTmp) {
        return FAILURE;
    }
    p = ht->pListHead;
    i = 0;
    while (p) {
        arTmp[i] = p;
        p = p->pListNext;
        i++;
    }

    (*sort_func)((void *) arTmp, i, sizeof(Bucket *), compar TSRMLS_CC);

    HANDLE_BLOCK_INTERRUPTIONS();
    ht->pListHead = arTmp[0];
    ht->pListTail = NULL;
    ht->pInternalPointer = ht->pListHead;

    arTmp[0]->pListLast = NULL;
    if (i > 1) {
        arTmp[0]->pListNext = arTmp[1];
        for (j = 1; j < i-1; j++) {
            arTmp[j]->pListLast = arTmp[j-1];
            arTmp[j]->pListNext = arTmp[j+1];
        }
        arTmp[j]->pListLast = arTmp[j-1];
        arTmp[j]->pListNext = NULL;
    } else {
        arTmp[0]->pListNext = NULL;
    }
    ht->pListTail = arTmp[i-1];

    pefree(arTmp, ht->persistent);
    HANDLE_UNBLOCK_INTERRUPTIONS();

    if (renumber) {
        p = ht->pListHead;
        i=0;
        while (p != NULL) {
            p->nKeyLength = 0;
            p->h = i++;
            p = p->pListNext;
        }
        ht->nNextFreeElement = i;
        zend_hash_rehash(ht);
    }
    return SUCCESS;
}


ZEND_API int zend_hash_compare(HashTable *ht1, HashTable *ht2, compare_func_t compar, zend_bool ordered TSRMLS_DC)
{
    Bucket *p1, *p2 = NULL;
    int result;
    void *pData2;

    IS_CONSISTENT(ht1);
    IS_CONSISTENT(ht2);

    HASH_PROTECT_RECURSION(ht1);
    HASH_PROTECT_RECURSION(ht2);

    result = ht1->nNumOfElements - ht2->nNumOfElements;
    if (result!=0) {
        HASH_UNPROTECT_RECURSION(ht1);
        HASH_UNPROTECT_RECURSION(ht2);
        return result;
    }

    p1 = ht1->pListHead;
    if (ordered) {
        p2 = ht2->pListHead;
    }

    while (p1) {
        if (ordered && !p2) {
            HASH_UNPROTECT_RECURSION(ht1);
            HASH_UNPROTECT_RECURSION(ht2);
            return 1; /* That's not supposed to happen */
        }
        if (ordered) {
            if (p1->nKeyLength==0 && p2->nKeyLength==0) { /* numeric indices */
                result = p1->h - p2->h;
                if (result!=0) {
                    HASH_UNPROTECT_RECURSION(ht1);
                    HASH_UNPROTECT_RECURSION(ht2);
                    return result;
                }
            } else { /* string indices */
                result = p1->nKeyLength - p2->nKeyLength;
                if (result!=0) {
                    HASH_UNPROTECT_RECURSION(ht1);
                    HASH_UNPROTECT_RECURSION(ht2);
                    return result;
                }
                result = memcmp(p1->arKey, p2->arKey, p1->nKeyLength);
                if (result!=0) {
                    HASH_UNPROTECT_RECURSION(ht1);
                    HASH_UNPROTECT_RECURSION(ht2);
                    return result;
                }
            }
            pData2 = p2->pData;
        } else {
            if (p1->nKeyLength==0) { /* numeric index */
                if (zend_hash_index_find(ht2, p1->h, &pData2)==FAILURE) {
                    HASH_UNPROTECT_RECURSION(ht1);
                    HASH_UNPROTECT_RECURSION(ht2);
                    return 1;
                }
            } else { /* string index */
                if (zend_hash_quick_find(ht2, p1->arKey, p1->nKeyLength, p1->h, &pData2)==FAILURE) {
                    HASH_UNPROTECT_RECURSION(ht1);
                    HASH_UNPROTECT_RECURSION(ht2);
                    return 1;
                }
            }
        }
        result = compar(p1->pData, pData2 TSRMLS_CC);
        if (result!=0) {
            HASH_UNPROTECT_RECURSION(ht1);
            HASH_UNPROTECT_RECURSION(ht2);
            return result;
        }
        p1 = p1->pListNext;
        if (ordered) {
            p2 = p2->pListNext;
        }
    }
    
    HASH_UNPROTECT_RECURSION(ht1);
    HASH_UNPROTECT_RECURSION(ht2);
    return 0;
}


ZEND_API int zend_hash_minmax(const HashTable *ht, compare_func_t compar, int flag, void **pData TSRMLS_DC)
{
    Bucket *p, *res;

    IS_CONSISTENT(ht);

    if (ht->nNumOfElements == 0 ) {
        *pData=NULL;
        return FAILURE;
    }

    res = p = ht->pListHead;
    while ((p = p->pListNext)) {
        if (flag) {
            if (compar(&res, &p TSRMLS_CC) < 0) { /* max */
                res = p;
            }
        } else {
            if (compar(&res, &p TSRMLS_CC) > 0) { /* min */
                res = p;
            }
        }
    }
    *pData = res->pData;
    return SUCCESS;
}

ZEND_API ulong zend_hash_next_free_element(const HashTable *ht)
{
    IS_CONSISTENT(ht);

    return ht->nNextFreeElement;

}


#if ZEND_DEBUG
void zend_hash_display_pListTail(const HashTable *ht)
{
    Bucket *p;

    p = ht->pListTail;
    while (p != NULL) {
        zend_output_debug_string(0, "pListTail has key %s\n", p->arKey);
        p = p->pListLast;
    }
}

void zend_hash_display(const HashTable *ht)
{
    Bucket *p;
    uint i;

    if (UNEXPECTED(ht->nNumOfElements == 0)) {
        zend_output_debug_string(0, "The hash is empty");
        return;
    }
    for (i = 0; i < ht->nTableSize; i++) {
        p = ht->arBuckets[i];
        while (p != NULL) {
            zend_output_debug_string(0, "%s <==> 0x%lX\n", p->arKey, p->h);
            p = p->pNext;
        }
    }

    p = ht->pListTail;
    while (p != NULL) {
        zend_output_debug_string(0, "%s <==> 0x%lX\n", p->arKey, p->h);
        p = p->pListLast;
    }
}
#endif

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * indent-tabs-mode: t
 * End:
 */

转载于:https://www.cnblogs.com/sevensole7/archive/2013/06/03/3115288.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值