We fulfill power fantasies

RSS Feed Back

Generic hashtable in C (2): the final API

14.8.2019 18:00:23

Some time back I wrote a generic hashtable in C. Since that post, the API has seen some iteration as I've been using the code in my projects.

This hashtable heavily relies on macros that call type-generic functions which modify struct members based on their offsets and do other evil stuff of that sort, all in a way that isn't necessarily completely standard-conforming nor the most efficient way to do things. In exchange, it accepts any kind of data as keys or values, and accepts any hash function that returns a size_t. Below's a short description of the final API that differs a little from the one described in the original post.

Initialization

hashtable(uint64_t, uint32_t) table;
int err;
hashtable_init(table, 16, &err);
if (err)
   /* Handle error */

Insertion

int         err;
uint64_t    key     = 12345;
uint32_t    value   = 54321;
size_t      hash    = hashtable_hash(&key, sizeof(key));
hashtable_insert(table, key, hash, value, &err);
if (err)
   /* Handle error */

Searching

uint64_t    key     = 12345;
size_t      hash    = hashtable_hash(&key, sizeof(key));
uint32_t    *value  = hashtable_find(table, key, hash);
if (value)
   /* Do something with value. */

Erasing

uint64_t    key  = 12345;
size_t      hash = hashtable_hash(&key, sizeof(key));
hashtable_erase(table, key, hash);

Iteration

uint64_t key;
uint32_t value;
hashtable_for_each_pair(table, key, value) {
   /* Do something with key and value */
}

Strings and other comlex data types

This hashtable saves a copy of each key in its entirety. For non-fixes size types, three functions must be defined compare_keys, copy_key and free_key. Below is an example of insertion into and erasure from a string-int table.

Allocation and copying function examples

int compare_keys(const void *a, const void *b, size_t size)
   {return strcmp(*(const char**)a, *(const char**)b);}

int copy_key(void *dst, const void *src, size_t size)
{
   size_t len = strlen(*(const char**)src);
   *(char**)dst = malloc(len + 1);
   if (!*(char**)dst)
       return 1;
   memcpy(*(char**)dst, *(const char**)src, len + 1);
   return 0;
}

void free_key(void *key)
   {free(*(char**)key);}

Insertion

char    *key    = "one";
size_t  hash    = hashtable_hash(key, strlen(key));
int     value   = 1;
hashtable_insert_ext(table, key, hash, value, compare_keys, copy_key,
   &err);
if (err)
   return -1;

Erasing

char    *key = "one";
size_t  hash = hashtable_hash(key, strlen(key));
hashtable_erase_ext(table, key, hash, compare_keys, free_key);

Typesafe function declarators

Finally, there's a couple of convenience macros for defining type-safe inline functions for tables of a given type. Their purpose is to reduce the need to type in all of the macro parameters every time as there can be quite a lot of them otherwise.

/* Define a struct named struct str_int_table and a set of functions for it */
hashtable_define_ext(str_int_table, const char *, int, compute_hash,
   compare_keys, copy_key, free_key);

...

struct str_int_table table;

if (str_int_table_init(&table, 8))
   /* Handle error */

if (str_int_table_insert(&table, "one", 1))
   /* Handle error */

Might still be bugs in the code, but I'll fix 'em as I come across 'em.

hashtable.h
hashtable.c
git