From 854fbf371cbce6d8c37a54ad1d2406b1adc7a6c6 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Nov 2023 15:07:08 -0500 Subject: [PATCH] Add string-keyed map and fixed memory leak --- libXmd/include/Xmd/StringMap.h | 39 ++++++++ libXmd/src/Map.c | 4 +- libXmd/src/StringMap.c | 157 +++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 libXmd/include/Xmd/StringMap.h create mode 100644 libXmd/src/StringMap.c diff --git a/libXmd/include/Xmd/StringMap.h b/libXmd/include/Xmd/StringMap.h new file mode 100644 index 0000000..3d5aa9b --- /dev/null +++ b/libXmd/include/Xmd/StringMap.h @@ -0,0 +1,39 @@ +#ifndef _XmdStringMap_h +#define _XmdStringMap_h + +#include + +/* XmdStringMap is a hash map that is keyed by a string and can store any type + of element. */ +typedef struct _XmdStringMap XmdStringMap; + +/* XmdStringMapIterator is a function that can iterate over a string map. */ +typedef Bool (*XmdStringMapIterator) ( + XmdStringMap *map, String key, void *value); + +/* XmdStringMapNew creates a new map.*/ +XmdStringMap *XmdStringMapNew (); + +/* XmdStringMapGet retrieves a value from a map. */ +void *XmdStringMapGet (XmdStringMap *map, String key); + +/* XmdStringMapSet sets a value in a map. The previous value associated with the + key, if it exists, is overwritten by the new value. If the value is set to + NULL, the key/value combination is deleted from the map. This function + returns the previous value associated with the key so it can be free'd if + necessary. + */ +void *XmdStringMapSet (XmdStringMap *map, String key, void *value); + +/* XmdStringMapIterate calls iterator for each key/value pair in a map. */ +void XmdStringMapIterate (XmdStringMap *map, XmdStringMapIterator iterator); + +/* XmdStringMapLength returns the number of key/value entries stored in a map. + */ +Cardinal XmdStringMapLength (XmdStringMap *map); + +/* XmdStringMapFree frees the map and any data associated with it. Note that + this will not free any of the map's values. */ +void XmdStringMapFree (XmdStringMap *map); + +#endif diff --git a/libXmd/src/Map.c b/libXmd/src/Map.c index b29ea7b..2810998 100644 --- a/libXmd/src/Map.c +++ b/libXmd/src/Map.c @@ -133,7 +133,9 @@ void XmdMapResizeIfNeeded (XmdMap *map) { XmdMapElement *element = oldData[index]; while (element != NULL) { XmdMapSetInternal(map, element->key, element->value); - element = element->sister; + XmdMapElement *sister = element->sister; + XtFree((char *)(element)); + element = sister; } } diff --git a/libXmd/src/StringMap.c b/libXmd/src/StringMap.c new file mode 100644 index 0000000..e9bedf2 --- /dev/null +++ b/libXmd/src/StringMap.c @@ -0,0 +1,157 @@ +#include +#include + +#define XmdStringMap_GROWTH_FACTOR 2 +#define XmdStringMap_INITIAL_CAPACITY 16 + +typedef struct _XmdStringMapElement { + String key; + void *value; + struct _XmdStringMapElement *sister; +} XmdStringMapElement; + +struct _XmdStringMap { + Cardinal length; + Cardinal capacity; + XmdStringMapElement **data; +}; + +static void XmdStringMapResizeIfNeeded (XmdStringMap *map); +static Cardinal XmdStringMapHash (XmdStringMap *map, String key); + +XmdStringMap *XmdStringMapNew () { + XmdStringMap *map = XtNew(XmdStringMap); + if (map == NULL) goto fail; + + map->length = 0; + map->capacity = XmdStringMap_INITIAL_CAPACITY; + map->data = (XmdStringMapElement **) ( + XtCalloc(map->capacity, sizeof(XmdStringMapElement *))); + if (map->data == NULL) goto fail; + + return map; + + fail: + XmdStringMapFree(map); + return NULL; +} + +void *XmdStringMapGet (XmdStringMap *map, String key) { + XmdStringMapElement *element = map->data[XmdStringMapHash(map, key)]; + while (element != NULL) { + if (strcmp(element->key, key) == 0) return element->value; + element = element->sister; + } + return NULL; +} + +void *XmdStringMapSetInternal (XmdStringMap *map, String key, void *value) { + /* find bucket */ + Cardinal hash = XmdStringMapHash(map, key); + XmdStringMapElement **destination = &map->data[hash]; + /* find position in bucket */ + while (*destination != NULL) { + if (strcmp((*destination)->key, key) == 0) { + /* element exists already */ + void *previous = (*destination)->value; + if (value == NULL) { + /* if setting to NULL, free old element */ + XmdStringMapElement *element = *destination; + *destination = element->sister; + XtFree((char *)(element)); + } else { + /* otherwise, replace value */ + (*destination)->value = XtNewString(value); + } + return previous; + } + destination = &((*destination)->sister); + } + /* element does not exist already */ + if (value != NULL) { + /* allocate new element and append */ + *destination = XtNew(XmdStringMapElement); + (*destination)->key = XtNewString(key); + (*destination)->value = value; + (*destination)->sister = NULL; + } + return NULL; +} + +void *XmdStringMapSet (XmdStringMap *map, String key, void *value) { + void *previous = XmdStringMapSetInternal(map, key, value); + if (value == NULL) { + if (previous != NULL) map->length --; + } else { + if (previous == NULL) map->length ++; + } + XmdStringMapResizeIfNeeded(map); + return previous; +} + +void XmdStringMapIterate (XmdStringMap *map, XmdStringMapIterator iterator) { + for (Cardinal index = 0; index < map->capacity; index ++) { + XmdStringMapElement *element = map->data[index]; + while (element != NULL) { + if(!iterator(map, element->key, element->value)) break; + element = element->sister; + } + } +} + +Cardinal XmdStringMaplength (XmdStringMap *map) { + return map->length; +} + +void XmdStringMapFree (XmdStringMap *map) { + if (map == NULL) return; + XtFree((char *)(map->data)); + XtFree((char *)(map)); +} + +void XmdStringMapResizeIfNeeded (XmdStringMap *map) { + Cardinal oldCapacity = map->capacity; + XmdStringMapElement **oldData = map->data; + + /* figure out what the new capacity should be. if the map length is + very small compared to the map capacity, shrink the map to save + memory. if the map length is getting large compared to the map + capacity, grow the map to reduce collisions. if the map length is + neither too big nor too small, do nothing because rehashing is an + expensive operation. */ + if (map->length + XmdStringMap_INITIAL_CAPACITY < map->capacity / 4) { + map->capacity /= XmdStringMap_GROWTH_FACTOR; + } else if (map->length > (map->capacity * 2) / 3) { + map->capacity *= XmdStringMap_GROWTH_FACTOR; + } else { + return; + } + + /* rehash */ + map->data = (XmdStringMapElement **) ( + XtCalloc(map->capacity, sizeof(XmdStringMapElement))); + for (Cardinal index = 0; index < oldCapacity; index ++) { + XmdStringMapElement *element = oldData[index]; + while (element != NULL) { + XmdStringMapSetInternal ( + map, + element->key, element->value); + XmdStringMapElement *sister = element->sister; + XtFree(element->key); + XtFree((char *)(element)); + element = sister; + } + } + + XtFree((char *)(oldData)); +} + +static Cardinal XmdStringMapHash (XmdStringMap *map, String key) { + /* http://www.cse.yorku.ca/~oz/hash.html */ + Cardinal hash = 5381; + Cardinal ch; + while ((ch = (Cardinal)(*key ++)) != 0) { + hash = ((hash << 5) + hash) + ch; + } + return hash % map->capacity; +}