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.
//
// [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
*/