Add string-keyed map and fixed memory leak

This commit is contained in:
Sasha Koshka 2023-11-12 15:07:08 -05:00
parent edea2a350d
commit 854fbf371c
3 changed files with 199 additions and 1 deletions

View File

@ -0,0 +1,39 @@
#ifndef _XmdStringMap_h
#define _XmdStringMap_h
#include <X11/Intrinsic.h>
/* 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

View File

@ -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;
}
}

157
libXmd/src/StringMap.c Normal file
View File

@ -0,0 +1,157 @@
#include <Xmd/StringMap.h>
#include <X11/Intrinsic.h>
#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;
}