This commit is contained in:
seajee
2026-01-10 17:01:21 +01:00
parent 746ba23d9d
commit 8094fcf08f

105
hm.h
View File

@@ -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. // chained hash table implementation as a single header library.
// //
// [License and changelog] // [License and changelog]
@@ -74,8 +74,8 @@
// bool hm_put(HashMap *hm, const void *key, const void *value) // bool hm_put(HashMap *hm, const void *key, const void *value)
// //
// This function inserts a new value associated with the specified key. If the // 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 // key already exists, it's value will be overridden. Returns false on error
// only if HM_NO_ASSERT is not defined. // only if HM_NO_ASSERT is defined.
// //
// void *hm_get(const HashMap *hm, const void *key) // void *hm_get(const HashMap *hm, const void *key)
// //
@@ -87,10 +87,16 @@
// This function removes the entry associated with the specified key. Returns // This function removes the entry associated with the specified key. Returns
// whether the key was found and removed or not. // 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) // HashMapIterator hm_iterate(const HashMap *hm)
// //
// This function creates a HashMapIterator used to iterate the specfied // 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) // const void *hm_key(const HashMapIterator *it)
// //
@@ -106,6 +112,10 @@
// //
// This function iterates through the next entry of the HashMap and returns // 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. // it's value. Returns NULL if the iterator reached the end of the HashMap.
//
// [Notes]
//
// This library is not thread-safe.
#ifndef HM_H_ #ifndef HM_H_
#define 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); bool hm_put(HashMap *hm, const void *key, const void *value);
void *hm_get(const HashMap *hm, const void *key); void *hm_get(const HashMap *hm, const void *key);
bool hm_remove(HashMap *hm, const void *key); bool hm_remove(HashMap *hm, const void *key);
size_t hm_count(const HashMap *hm);
// Iterator functions // Iterator functions
HashMapIterator hm_iterate(const HashMap *hm); 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) void hm_free(HashMap *hm)
{ {
if (hm == NULL || hm->map == NULL) { if (hm == NULL) {
return;
}
if (hm->map == NULL) {
memset(hm, 0, sizeof(*hm)); memset(hm, 0, sizeof(*hm));
return; return;
} }
@@ -253,12 +268,12 @@ bool hm_put(HashMap *hm, const void *key, const void *value)
// Ensure that HashMap is initialised // Ensure that HashMap is initialised
if (hm->map == NULL) { 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) { if (hm->map == NULL) {
HM_ASSERT(false && "Reallocation failed"); HM_ASSERT(false && "Reallocation failed");
return false; return false;
} }
hm->capacity = HM_INITIAL_CAPACITY;
memset(hm->map, 0, sizeof(*hm->map) * hm->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 // 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); memcpy(cur->value, value, value_size);
return true; 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); uint8_t *new_value = (uint8_t*)HM_REALLOC(cur->value, value_size);
if (new_value == NULL) { if (new_value == NULL) {
HM_ASSERT(false && "Reallocation failed"); HM_ASSERT(false && "Reallocation failed");
HM_FREE(tmp);
return false; return false;
} }
cur->value = new_value; cur->value = new_value;
@@ -394,6 +410,15 @@ bool hm_remove(HashMap *hm, const void *key)
return false; return false;
} }
size_t hm_count(const HashMap *hm)
{
if (hm == NULL) {
return 0;
}
return hm->count;
}
HashMapIterator hm_iterate(const HashMap *hm) HashMapIterator hm_iterate(const HashMap *hm)
{ {
HashMapIterator it; HashMapIterator it;
@@ -415,8 +440,8 @@ const void *hm_key(const HashMapIterator *it)
return NULL; return NULL;
} }
// Reached the end // Error or reached the end
if (it->end) { if (it->end || it->bucket == NULL) {
return NULL; return NULL;
} }
@@ -430,8 +455,8 @@ void *hm_value(const HashMapIterator *it)
return NULL; return NULL;
} }
// Reached the end // Error or reached the end
if (it->end) { if (it->end || it->bucket == NULL) {
return NULL; return NULL;
} }
@@ -450,29 +475,48 @@ void *hm_next(HashMapIterator *it)
return NULL; return NULL;
} }
if (it->bucket == NULL) { const HashMap *hm = it->hm;
// Find first list of buckets
while ((it->bucket = it->hm->map[it->index++]) == NULL); // Empty HashMap
return it->bucket->value; 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) { if (it->bucket->next != NULL) {
it->bucket = it->bucket->next; it->bucket = it->bucket->next;
return it->bucket->value; return it->bucket->value;
} }
// Find next list of buckets // Move to next non-empty bucket array slot
while ((it->bucket = it->hm->map[++it->index]) == NULL); while (it->index < hm->capacity) {
it->bucket = hm->map[it->index++];
// Reached the end if (it->bucket != NULL) {
if (it->index >= it->hm->capacity) { return it->bucket->value;
it->end = true; }
return NULL;
} }
it->bucket = it->hm->map[it->index]; it->end = true;
return it->bucket->value; it->bucket = NULL;
it->index = hm->capacity;
return NULL;
} }
uint64_t hm__fnv1a(const void *buffer, size_t size) 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; return 0;
} }
// TODO: When buffer is NULL terminated, the NULL is included in the hash
const uint8_t *b = (const uint8_t*)buffer; const uint8_t *b = (const uint8_t*)buffer;
uint64_t hash = 14695981039346656037ULL; 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); HM_FREE(bucket);
return NULL; return NULL;
} }
memset(bucket->key, 0, key_size);
// Allocate value // Allocate value
bucket->value = HM_REALLOC(NULL, value_size); 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); HM_FREE(bucket);
return NULL; return NULL;
} }
memset(bucket->value, 0, value_size);
// Copy Key-value data // Copy Key-value data
memcpy(bucket->key, key, key_size); 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: * 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.1 (2025-12-30) Added documentation
* 1.0.0 (2025-11-18) Initial release * 1.0.0 (2025-11-18) Initial release
*/ */