[td-225] refactor hash function
This commit is contained in:
parent
5f0a05163d
commit
c6aef80a39
|
@ -94,7 +94,7 @@ size_t taosHashGetSize(const SHashObj *pHashObj);
|
|||
* @param size
|
||||
* @return
|
||||
*/
|
||||
int32_t taosHashPut(SHashObj *pHashObj, const char *key, size_t keyLen, void *data, size_t size);
|
||||
int32_t taosHashPut(SHashObj *pHashObj, const void *key, size_t keyLen, void *data, size_t size);
|
||||
|
||||
/**
|
||||
* return the payload data with the specified key
|
||||
|
@ -104,7 +104,7 @@ int32_t taosHashPut(SHashObj *pHashObj, const char *key, size_t keyLen, void *da
|
|||
* @param keyLen
|
||||
* @return
|
||||
*/
|
||||
void *taosHashGet(SHashObj *pHashObj, const char *key, size_t keyLen);
|
||||
void *taosHashGet(SHashObj *pHashObj, const void *key, size_t keyLen);
|
||||
|
||||
/**
|
||||
* remove item with the specified key
|
||||
|
@ -112,7 +112,7 @@ void *taosHashGet(SHashObj *pHashObj, const char *key, size_t keyLen);
|
|||
* @param key
|
||||
* @param keyLen
|
||||
*/
|
||||
void taosHashRemove(SHashObj *pHashObj, const char *key, size_t keyLen);
|
||||
void taosHashRemove(SHashObj *pHashObj, const void *key, size_t keyLen);
|
||||
|
||||
/**
|
||||
* clean up hash table
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
#include "hash.h"
|
||||
#include "tulog.h"
|
||||
#include "ttime.h"
|
||||
#include "tutil.h"
|
||||
|
||||
static FORCE_INLINE void __wr_lock(void *lock) {
|
||||
|
@ -90,154 +89,65 @@ static FORCE_INLINE int32_t taosHashCapacity(int32_t length) {
|
|||
|
||||
/**
|
||||
* inplace update node in hash table
|
||||
* @param pHashObj hash table object
|
||||
* @param pNode data node
|
||||
* @param pHashObj hash table object
|
||||
* @param pNode hash data node
|
||||
*/
|
||||
static void doUpdateHashTable(SHashObj *pHashObj, SHashNode *pNode) {
|
||||
if (pNode->prev1) {
|
||||
pNode->prev1->next = pNode;
|
||||
}
|
||||
|
||||
if (pNode->next) {
|
||||
(pNode->next)->prev = pNode;
|
||||
}
|
||||
}
|
||||
static void doUpdateHashTable(SHashObj *pHashObj, SHashNode *pNode);
|
||||
|
||||
/**
|
||||
* get SHashNode from hashlist, nodes from trash are not included.
|
||||
* Get SHashNode from hashlist, nodes from trash are not included.
|
||||
* @param pHashObj Cache objection
|
||||
* @param key key for hash
|
||||
* @param keyLen key length
|
||||
* @param hashVal hash value by hash function
|
||||
* @return
|
||||
*/
|
||||
static SHashNode *doGetNodeFromHashTable(SHashObj *pHashObj, const char *key, uint32_t keyLen, uint32_t *hashVal) {
|
||||
uint32_t hash = (*pHashObj->hashFp)(key, keyLen);
|
||||
|
||||
int32_t slot = HASH_INDEX(hash, pHashObj->capacity);
|
||||
SHashEntry *pEntry = pHashObj->hashList[slot];
|
||||
|
||||
SHashNode *pNode = pEntry->next;
|
||||
while (pNode) {
|
||||
if ((pNode->keyLen == keyLen) && (memcmp(pNode->key, key, keyLen) == 0)) {
|
||||
break;
|
||||
}
|
||||
|
||||
pNode = pNode->next;
|
||||
}
|
||||
|
||||
if (pNode) {
|
||||
assert(HASH_INDEX(pNode->hashVal, pHashObj->capacity) == slot);
|
||||
}
|
||||
|
||||
// return the calculated hash value, to avoid calculating it again in other functions
|
||||
if (hashVal != NULL) {
|
||||
*hashVal = hash;
|
||||
}
|
||||
|
||||
return pNode;
|
||||
}
|
||||
static SHashNode *doGetNodeFromHashTable(SHashObj *pHashObj, const void *key, uint32_t keyLen, uint32_t *hashVal);
|
||||
|
||||
/**
|
||||
* resize the hash list if the threshold is reached
|
||||
* Resize the hash list if the threshold is reached
|
||||
*
|
||||
* @param pHashObj
|
||||
*/
|
||||
static void taosHashTableResize(SHashObj *pHashObj) {
|
||||
if (pHashObj->size < pHashObj->capacity * HASH_DEFAULT_LOAD_FACTOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
// double the original capacity
|
||||
SHashNode *pNode = NULL;
|
||||
SHashNode *pNext = NULL;
|
||||
|
||||
int32_t newSize = pHashObj->capacity << 1u;
|
||||
if (newSize > HASH_MAX_CAPACITY) {
|
||||
// uTrace("current capacity:%d, maximum capacity:%d, no resize applied due to limitation is reached",
|
||||
// pHashObj->capacity, HASH_MAX_CAPACITY);
|
||||
return;
|
||||
}
|
||||
|
||||
// int64_t st = taosGetTimestampUs();
|
||||
|
||||
SHashEntry **pNewEntry = realloc(pHashObj->hashList, sizeof(SHashEntry *) * newSize);
|
||||
if (pNewEntry == NULL) {
|
||||
// uTrace("cache resize failed due to out of memory, capacity remain:%d", pHashObj->capacity);
|
||||
return;
|
||||
}
|
||||
|
||||
pHashObj->hashList = pNewEntry;
|
||||
for (int32_t i = pHashObj->capacity; i < newSize; ++i) {
|
||||
pHashObj->hashList[i] = calloc(1, sizeof(SHashEntry));
|
||||
}
|
||||
|
||||
pHashObj->capacity = newSize;
|
||||
|
||||
for (int32_t i = 0; i < pHashObj->capacity; ++i) {
|
||||
SHashEntry *pEntry = pHashObj->hashList[i];
|
||||
|
||||
pNode = pEntry->next;
|
||||
if (pNode != NULL) {
|
||||
assert(pNode->prev1 == pEntry && pEntry->num > 0);
|
||||
}
|
||||
|
||||
while (pNode) {
|
||||
int32_t j = HASH_INDEX(pNode->hashVal, pHashObj->capacity);
|
||||
if (j == i) { // this key resides in the same slot, no need to relocate it
|
||||
pNode = pNode->next;
|
||||
} else {
|
||||
pNext = pNode->next;
|
||||
|
||||
// remove from current slot
|
||||
assert(pNode->prev1 != NULL);
|
||||
|
||||
if (pNode->prev1 == pEntry) { // first node of the overflow linked list
|
||||
pEntry->next = pNode->next;
|
||||
} else {
|
||||
pNode->prev->next = pNode->next;
|
||||
}
|
||||
|
||||
pEntry->num--;
|
||||
assert(pEntry->num >= 0);
|
||||
|
||||
if (pNode->next != NULL) {
|
||||
(pNode->next)->prev = pNode->prev;
|
||||
}
|
||||
|
||||
// added into new slot
|
||||
pNode->next = NULL;
|
||||
pNode->prev1 = NULL;
|
||||
|
||||
SHashEntry *pNewIndexEntry = pHashObj->hashList[j];
|
||||
|
||||
if (pNewIndexEntry->next != NULL) {
|
||||
assert(pNewIndexEntry->next->prev1 == pNewIndexEntry);
|
||||
|
||||
pNewIndexEntry->next->prev = pNode;
|
||||
}
|
||||
|
||||
pNode->next = pNewIndexEntry->next;
|
||||
pNode->prev1 = pNewIndexEntry;
|
||||
|
||||
pNewIndexEntry->next = pNode;
|
||||
pNewIndexEntry->num++;
|
||||
|
||||
// continue
|
||||
pNode = pNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// int64_t et = taosGetTimestampUs();
|
||||
// uTrace("hash table resize completed, new capacity:%d, load factor:%f, elapsed time:%fms", pHashObj->capacity,
|
||||
// ((double)pHashObj->size) / pHashObj->capacity, (et - st) / 1000.0);
|
||||
}
|
||||
static void taosHashTableResize(SHashObj *pHashObj);
|
||||
|
||||
/**
|
||||
* @param capacity maximum slots available for hash elements
|
||||
* @param fn hash function
|
||||
* @param key key of object for hash, usually a null-terminated string
|
||||
* @param keyLen length of key
|
||||
* @param pData actually data. Requires a consecutive memory block, no pointer is allowed in pData.
|
||||
* Pointer copy causes memory access error.
|
||||
* @param dsize size of data
|
||||
* @return SHashNode
|
||||
*/
|
||||
static SHashNode *doCreateHashNode(const void *key, size_t keyLen, const void *pData, size_t dsize, uint32_t hashVal);
|
||||
|
||||
/**
|
||||
* Update the hash node
|
||||
*
|
||||
* @param pNode hash node
|
||||
* @param key key for generate hash value
|
||||
* @param keyLen key length
|
||||
* @param pData actual data
|
||||
* @param dsize size of actual data
|
||||
* @return hash node
|
||||
*/
|
||||
static SHashNode *doUpdateHashNode(SHashNode *pNode, const void *key, size_t keyLen, const void *pData, size_t dsize);
|
||||
|
||||
/**
|
||||
* insert the hash node at the front of the linked list
|
||||
*
|
||||
* @param pHashObj
|
||||
* @param pNode
|
||||
*/
|
||||
static void doAddToHashTable(SHashObj *pHashObj, SHashNode *pNode);
|
||||
|
||||
/**
|
||||
* Get the next element in hash table for iterator
|
||||
* @param pIter
|
||||
* @return
|
||||
*/
|
||||
static SHashNode *getNextHashNode(SHashMutableIterator *pIter);
|
||||
|
||||
SHashObj *taosHashInit(size_t capacity, _hash_fn_t fn, bool threadsafe) {
|
||||
if (capacity == 0 || fn == NULL) {
|
||||
return NULL;
|
||||
|
@ -285,79 +195,6 @@ SHashObj *taosHashInit(size_t capacity, _hash_fn_t fn, bool threadsafe) {
|
|||
return pHashObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key key of object for hash, usually a null-terminated string
|
||||
* @param keyLen length of key
|
||||
* @param pData actually data. required a consecutive memory block, no pointer is allowed
|
||||
* in pData. Pointer copy causes memory access error.
|
||||
* @param size size of block
|
||||
* @return SHashNode
|
||||
*/
|
||||
static SHashNode *doCreateHashNode(const char *key, size_t keyLen, const char *pData, size_t dataSize,
|
||||
uint32_t hashVal) {
|
||||
size_t totalSize = dataSize + sizeof(SHashNode) + keyLen + 1; // one extra byte for null
|
||||
|
||||
SHashNode *pNewNode = calloc(1, totalSize);
|
||||
if (pNewNode == NULL) {
|
||||
uError("failed to allocate memory, reason:%s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(pNewNode->data, pData, dataSize);
|
||||
|
||||
pNewNode->key = pNewNode->data + dataSize;
|
||||
memcpy(pNewNode->key, key, keyLen);
|
||||
pNewNode->keyLen = keyLen;
|
||||
|
||||
pNewNode->hashVal = hashVal;
|
||||
|
||||
return pNewNode;
|
||||
}
|
||||
|
||||
static SHashNode *doUpdateHashNode(SHashNode *pNode, const char *key, size_t keyLen, const char *pData,
|
||||
size_t dataSize) {
|
||||
size_t size = dataSize + sizeof(SHashNode) + keyLen;
|
||||
|
||||
SHashNode *pNewNode = (SHashNode *)realloc(pNode, size);
|
||||
if (pNewNode == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(pNewNode->data, pData, dataSize);
|
||||
|
||||
pNewNode->key = pNewNode->data + dataSize;
|
||||
|
||||
assert(memcmp(pNewNode->key, key, keyLen) == 0 && keyLen == pNewNode->keyLen);
|
||||
|
||||
memcpy(pNewNode->key, key, keyLen);
|
||||
return pNewNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* insert the hash node at the front of the linked list
|
||||
*
|
||||
* @param pHashObj
|
||||
* @param pNode
|
||||
*/
|
||||
static void doAddToHashTable(SHashObj *pHashObj, SHashNode *pNode) {
|
||||
assert(pNode != NULL);
|
||||
|
||||
int32_t index = HASH_INDEX(pNode->hashVal, pHashObj->capacity);
|
||||
SHashEntry *pEntry = pHashObj->hashList[index];
|
||||
|
||||
pNode->next = pEntry->next;
|
||||
|
||||
if (pEntry->next) {
|
||||
pEntry->next->prev = pNode;
|
||||
}
|
||||
|
||||
pEntry->next = pNode;
|
||||
pNode->prev1 = pEntry;
|
||||
|
||||
pEntry->num++;
|
||||
pHashObj->size++;
|
||||
}
|
||||
|
||||
size_t taosHashGetSize(const SHashObj *pHashObj) {
|
||||
if (pHashObj == NULL) {
|
||||
return 0;
|
||||
|
@ -366,12 +203,7 @@ size_t taosHashGetSize(const SHashObj *pHashObj) {
|
|||
return pHashObj->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* add data node into hash table
|
||||
* @param pHashObj hash object
|
||||
* @param pNode hash node
|
||||
*/
|
||||
int32_t taosHashPut(SHashObj *pHashObj, const char *key, size_t keyLen, void *data, size_t size) {
|
||||
int32_t taosHashPut(SHashObj *pHashObj, const void *key, size_t keyLen, void *data, size_t size) {
|
||||
__wr_lock(pHashObj->lock);
|
||||
|
||||
uint32_t hashVal = 0;
|
||||
|
@ -402,7 +234,7 @@ int32_t taosHashPut(SHashObj *pHashObj, const char *key, size_t keyLen, void *da
|
|||
return 0;
|
||||
}
|
||||
|
||||
void *taosHashGet(SHashObj *pHashObj, const char *key, size_t keyLen) {
|
||||
void *taosHashGet(SHashObj *pHashObj, const void *key, size_t keyLen) {
|
||||
__rd_lock(pHashObj->lock);
|
||||
|
||||
uint32_t hashVal = 0;
|
||||
|
@ -419,12 +251,7 @@ void *taosHashGet(SHashObj *pHashObj, const char *key, size_t keyLen) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove node in hash list
|
||||
* @param pHashObj
|
||||
* @param pNode
|
||||
*/
|
||||
void taosHashRemove(SHashObj *pHashObj, const char *key, size_t keyLen) {
|
||||
void taosHashRemove(SHashObj *pHashObj, const void *key, size_t keyLen) {
|
||||
__wr_lock(pHashObj->lock);
|
||||
|
||||
uint32_t val = 0;
|
||||
|
@ -518,23 +345,6 @@ SHashMutableIterator *taosHashCreateIter(SHashObj *pHashObj) {
|
|||
return pIter;
|
||||
}
|
||||
|
||||
static SHashNode *getNextHashNode(SHashMutableIterator *pIter) {
|
||||
assert(pIter != NULL);
|
||||
|
||||
pIter->entryIndex++;
|
||||
while (pIter->entryIndex < pIter->pHashObj->capacity) {
|
||||
SHashEntry *pEntry = pIter->pHashObj->hashList[pIter->entryIndex];
|
||||
if (pEntry->next == NULL) {
|
||||
pIter->entryIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
return pEntry->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool taosHashIterNext(SHashMutableIterator *pIter) {
|
||||
if (pIter == NULL) {
|
||||
return false;
|
||||
|
@ -617,3 +427,205 @@ int32_t taosHashGetMaxOverflowLinkLength(const SHashObj *pHashObj) {
|
|||
|
||||
return num;
|
||||
}
|
||||
|
||||
void doUpdateHashTable(SHashObj *pHashObj, SHashNode *pNode) {
|
||||
if (pNode->prev1) {
|
||||
pNode->prev1->next = pNode;
|
||||
}
|
||||
|
||||
if (pNode->next) {
|
||||
(pNode->next)->prev = pNode;
|
||||
}
|
||||
}
|
||||
|
||||
SHashNode *doGetNodeFromHashTable(SHashObj *pHashObj, const void *key, uint32_t keyLen, uint32_t *hashVal) {
|
||||
uint32_t hash = (*pHashObj->hashFp)(key, keyLen);
|
||||
|
||||
int32_t slot = HASH_INDEX(hash, pHashObj->capacity);
|
||||
SHashEntry *pEntry = pHashObj->hashList[slot];
|
||||
|
||||
SHashNode *pNode = pEntry->next;
|
||||
while (pNode) {
|
||||
if ((pNode->keyLen == keyLen) && (memcmp(pNode->key, key, keyLen) == 0)) {
|
||||
break;
|
||||
}
|
||||
|
||||
pNode = pNode->next;
|
||||
}
|
||||
|
||||
if (pNode) {
|
||||
assert(HASH_INDEX(pNode->hashVal, pHashObj->capacity) == slot);
|
||||
}
|
||||
|
||||
// return the calculated hash value, to avoid calculating it again in other functions
|
||||
if (hashVal != NULL) {
|
||||
*hashVal = hash;
|
||||
}
|
||||
|
||||
return pNode;
|
||||
}
|
||||
|
||||
void taosHashTableResize(SHashObj *pHashObj) {
|
||||
if (pHashObj->size < pHashObj->capacity * HASH_DEFAULT_LOAD_FACTOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
// double the original capacity
|
||||
SHashNode *pNode = NULL;
|
||||
SHashNode *pNext = NULL;
|
||||
|
||||
int32_t newSize = pHashObj->capacity << 1u;
|
||||
if (newSize > HASH_MAX_CAPACITY) {
|
||||
// uTrace("current capacity:%d, maximum capacity:%d, no resize applied due to limitation is reached",
|
||||
// pHashObj->capacity, HASH_MAX_CAPACITY);
|
||||
return;
|
||||
}
|
||||
|
||||
// int64_t st = taosGetTimestampUs();
|
||||
|
||||
SHashEntry **pNewEntry = realloc(pHashObj->hashList, sizeof(SHashEntry *) * newSize);
|
||||
if (pNewEntry == NULL) {
|
||||
// uTrace("cache resize failed due to out of memory, capacity remain:%d", pHashObj->capacity);
|
||||
return;
|
||||
}
|
||||
|
||||
pHashObj->hashList = pNewEntry;
|
||||
for (int32_t i = pHashObj->capacity; i < newSize; ++i) {
|
||||
pHashObj->hashList[i] = calloc(1, sizeof(SHashEntry));
|
||||
}
|
||||
|
||||
pHashObj->capacity = newSize;
|
||||
|
||||
for (int32_t i = 0; i < pHashObj->capacity; ++i) {
|
||||
SHashEntry *pEntry = pHashObj->hashList[i];
|
||||
|
||||
pNode = pEntry->next;
|
||||
if (pNode != NULL) {
|
||||
assert(pNode->prev1 == pEntry && pEntry->num > 0);
|
||||
}
|
||||
|
||||
while (pNode) {
|
||||
int32_t j = HASH_INDEX(pNode->hashVal, pHashObj->capacity);
|
||||
if (j == i) { // this key resides in the same slot, no need to relocate it
|
||||
pNode = pNode->next;
|
||||
} else {
|
||||
pNext = pNode->next;
|
||||
|
||||
// remove from current slot
|
||||
assert(pNode->prev1 != NULL);
|
||||
|
||||
if (pNode->prev1 == pEntry) { // first node of the overflow linked list
|
||||
pEntry->next = pNode->next;
|
||||
} else {
|
||||
pNode->prev->next = pNode->next;
|
||||
}
|
||||
|
||||
pEntry->num--;
|
||||
assert(pEntry->num >= 0);
|
||||
|
||||
if (pNode->next != NULL) {
|
||||
(pNode->next)->prev = pNode->prev;
|
||||
}
|
||||
|
||||
// added into new slot
|
||||
pNode->next = NULL;
|
||||
pNode->prev1 = NULL;
|
||||
|
||||
SHashEntry *pNewIndexEntry = pHashObj->hashList[j];
|
||||
|
||||
if (pNewIndexEntry->next != NULL) {
|
||||
assert(pNewIndexEntry->next->prev1 == pNewIndexEntry);
|
||||
|
||||
pNewIndexEntry->next->prev = pNode;
|
||||
}
|
||||
|
||||
pNode->next = pNewIndexEntry->next;
|
||||
pNode->prev1 = pNewIndexEntry;
|
||||
|
||||
pNewIndexEntry->next = pNode;
|
||||
pNewIndexEntry->num++;
|
||||
|
||||
// continue
|
||||
pNode = pNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// int64_t et = taosGetTimestampUs();
|
||||
// uTrace("hash table resize completed, new capacity:%d, load factor:%f, elapsed time:%fms", pHashObj->capacity,
|
||||
// ((double)pHashObj->size) / pHashObj->capacity, (et - st) / 1000.0);
|
||||
}
|
||||
|
||||
SHashNode *doCreateHashNode(const void *key, size_t keyLen, const void *pData, size_t dsize, uint32_t hashVal) {
|
||||
size_t totalSize = dsize + sizeof(SHashNode) + keyLen + 1; // one extra byte for null
|
||||
|
||||
SHashNode *pNewNode = calloc(1, totalSize);
|
||||
if (pNewNode == NULL) {
|
||||
uError("failed to allocate memory, reason:%s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(pNewNode->data, pData, dsize);
|
||||
|
||||
pNewNode->key = pNewNode->data + dsize;
|
||||
memcpy(pNewNode->key, key, keyLen);
|
||||
pNewNode->keyLen = keyLen;
|
||||
|
||||
pNewNode->hashVal = hashVal;
|
||||
|
||||
return pNewNode;
|
||||
}
|
||||
|
||||
SHashNode *doUpdateHashNode(SHashNode *pNode, const void *key, size_t keyLen, const void *pData, size_t dsize) {
|
||||
size_t size = dsize + sizeof(SHashNode) + keyLen;
|
||||
|
||||
SHashNode *pNewNode = (SHashNode *)realloc(pNode, size);
|
||||
if (pNewNode == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(pNewNode->data, pData, dsize);
|
||||
|
||||
pNewNode->key = pNewNode->data + dsize;
|
||||
|
||||
assert(memcmp(pNewNode->key, key, keyLen) == 0 && keyLen == pNewNode->keyLen);
|
||||
|
||||
memcpy(pNewNode->key, key, keyLen);
|
||||
return pNewNode;
|
||||
}
|
||||
|
||||
void doAddToHashTable(SHashObj *pHashObj, SHashNode *pNode) {
|
||||
assert(pNode != NULL);
|
||||
|
||||
int32_t index = HASH_INDEX(pNode->hashVal, pHashObj->capacity);
|
||||
SHashEntry *pEntry = pHashObj->hashList[index];
|
||||
|
||||
pNode->next = pEntry->next;
|
||||
|
||||
if (pEntry->next) {
|
||||
pEntry->next->prev = pNode;
|
||||
}
|
||||
|
||||
pEntry->next = pNode;
|
||||
pNode->prev1 = pEntry;
|
||||
|
||||
pEntry->num++;
|
||||
pHashObj->size++;
|
||||
}
|
||||
|
||||
SHashNode *getNextHashNode(SHashMutableIterator *pIter) {
|
||||
assert(pIter != NULL);
|
||||
|
||||
pIter->entryIndex++;
|
||||
while (pIter->entryIndex < pIter->pHashObj->capacity) {
|
||||
SHashEntry *pEntry = pIter->pHashObj->hashList[pIter->entryIndex];
|
||||
if (pEntry->next == NULL) {
|
||||
pIter->entryIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
return pEntry->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue