diff --git a/hm.h b/hm.h index a8b6f3e..2114615 100644 --- a/hm.h +++ b/hm.h @@ -1,4 +1,4 @@ -// hm.h - v1.0.1 - MIT License +// hm.h - v1.1.0 - MIT License // chained hash table implementation as a single header library. // // [License and changelog] @@ -74,8 +74,8 @@ // bool hm_put(HashMap *hm, const void *key, const void *value) // // This function inserts a new value associated with the specified key. If the -// key already exists, it's value will be overridden. Return false on error -// only if HM_NO_ASSERT is not defined. +// key already exists, it's value will be overridden. Returns false on error +// only if HM_NO_ASSERT is defined. // // void *hm_get(const HashMap *hm, const void *key) // @@ -87,10 +87,16 @@ // This function removes the entry associated with the specified key. Returns // whether the key was found and removed or not. // +// size_t hm_count(const HashMap *hm) +// +// Returns the number of elements stored in the specified HashMap. Returns 0 +// if the parameter is NULL. +// // HashMapIterator hm_iterate(const HashMap *hm) // // This function creates a HashMapIterator used to iterate the specfied -// HashMap. +// HashMap. Note that the returned iterator does not start from the first +// element of the HashMap. You'll need to call hm_next() at least once. // // const void *hm_key(const HashMapIterator *it) // @@ -106,6 +112,10 @@ // // This function iterates through the next entry of the HashMap and returns // it's value. Returns NULL if the iterator reached the end of the HashMap. +// +// [Notes] +// +// This library is not thread-safe. #ifndef HM_H_ #define HM_H_ @@ -181,6 +191,7 @@ void hm_free(HashMap *hm); bool hm_put(HashMap *hm, const void *key, const void *value); void *hm_get(const HashMap *hm, const void *key); bool hm_remove(HashMap *hm, const void *key); +size_t hm_count(const HashMap *hm); // Iterator functions HashMapIterator hm_iterate(const HashMap *hm); @@ -220,7 +231,11 @@ HashMap hm_create(size_t key_size, size_t value_size) void hm_free(HashMap *hm) { - if (hm == NULL || hm->map == NULL) { + if (hm == NULL) { + return; + } + + if (hm->map == NULL) { memset(hm, 0, sizeof(*hm)); return; } @@ -253,12 +268,12 @@ bool hm_put(HashMap *hm, const void *key, const void *value) // Ensure that HashMap is initialised if (hm->map == NULL) { - hm->map = (Hm__Bucket**)HM_REALLOC(NULL, sizeof(*hm->map) * HM_INITIAL_CAPACITY); + hm->capacity = HM_INITIAL_CAPACITY; + hm->map = (Hm__Bucket**)HM_REALLOC(NULL, sizeof(*hm->map) * hm->capacity); if (hm->map == NULL) { HM_ASSERT(false && "Reallocation failed"); return false; } - hm->capacity = HM_INITIAL_CAPACITY; memset(hm->map, 0, sizeof(*hm->map) * hm->capacity); } @@ -284,7 +299,7 @@ bool hm_put(HashMap *hm, const void *key, const void *value) } // Fixed or equal value_size - if (hm->value_size != 0 || value_size == cur->value_size) { + if (value_size == cur->value_size) { memcpy(cur->value, value, value_size); return true; } @@ -301,6 +316,7 @@ bool hm_put(HashMap *hm, const void *key, const void *value) uint8_t *new_value = (uint8_t*)HM_REALLOC(cur->value, value_size); if (new_value == NULL) { HM_ASSERT(false && "Reallocation failed"); + HM_FREE(tmp); return false; } cur->value = new_value; @@ -394,6 +410,15 @@ bool hm_remove(HashMap *hm, const void *key) return false; } +size_t hm_count(const HashMap *hm) +{ + if (hm == NULL) { + return 0; + } + + return hm->count; +} + HashMapIterator hm_iterate(const HashMap *hm) { HashMapIterator it; @@ -415,8 +440,8 @@ const void *hm_key(const HashMapIterator *it) return NULL; } - // Reached the end - if (it->end) { + // Error or reached the end + if (it->end || it->bucket == NULL) { return NULL; } @@ -430,8 +455,8 @@ void *hm_value(const HashMapIterator *it) return NULL; } - // Reached the end - if (it->end) { + // Error or reached the end + if (it->end || it->bucket == NULL) { return NULL; } @@ -450,29 +475,48 @@ void *hm_next(HashMapIterator *it) return NULL; } - if (it->bucket == NULL) { - // Find first list of buckets - while ((it->bucket = it->hm->map[it->index++]) == NULL); - return it->bucket->value; + const HashMap *hm = it->hm; + + // Empty HashMap + if (hm->count == 0) { + it->end = true; + it->bucket = NULL; + it->index = hm->capacity; + return NULL; } - // Next bucket + // Find first list of buckets when iterator hasn't started yet + if (it->bucket == NULL) { + while (it->index < hm->capacity) { + it->bucket = hm->map[it->index++]; + if (it->bucket != NULL) { + return it->bucket->value; + } + } + it->end = true; + it->bucket = NULL; + it->index = hm->capacity; + return NULL; + } + + // Advance within current chain if (it->bucket->next != NULL) { it->bucket = it->bucket->next; return it->bucket->value; } - // Find next list of buckets - while ((it->bucket = it->hm->map[++it->index]) == NULL); - - // Reached the end - if (it->index >= it->hm->capacity) { - it->end = true; - return NULL; + // Move to next non-empty bucket array slot + while (it->index < hm->capacity) { + it->bucket = hm->map[it->index++]; + if (it->bucket != NULL) { + return it->bucket->value; + } } - it->bucket = it->hm->map[it->index]; - return it->bucket->value; + it->end = true; + it->bucket = NULL; + it->index = hm->capacity; + return NULL; } uint64_t hm__fnv1a(const void *buffer, size_t size) @@ -482,6 +526,8 @@ uint64_t hm__fnv1a(const void *buffer, size_t size) return 0; } + // TODO: When buffer is NULL terminated, the NULL is included in the hash + const uint8_t *b = (const uint8_t*)buffer; uint64_t hash = 14695981039346656037ULL; @@ -593,7 +639,6 @@ Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *valu HM_FREE(bucket); return NULL; } - memset(bucket->key, 0, key_size); // Allocate value bucket->value = HM_REALLOC(NULL, value_size); @@ -603,7 +648,6 @@ Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *valu HM_FREE(bucket); return NULL; } - memset(bucket->value, 0, value_size); // Copy Key-value data memcpy(bucket->key, key, key_size); @@ -622,6 +666,11 @@ Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *valu /* * Revision history: * + * 1.1.0 (2026-01-10) Added new hm_count() function; + * Fix value_size comparison in hm_put(); + * Fix memory leak on failed reallocation in hm_put(); + * Fix out-of-bounds access in hm_next(); + * Improved documentation * 1.0.1 (2025-12-30) Added documentation * 1.0.0 (2025-11-18) Initial release */