From b23db618057b5aa8905c41df963d6fa865d82c8c Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sat, 6 Apr 2002 17:57:02 +0000 Subject: [PATCH] Added a generic universal multi-purpose magical hash table module. Look at introductory comments in lib/hashtable.h to see all the features. Generic programming in C is a real adventure, but an afternoon spent with CPP quirks is a holiday when compared with C++ templates :-) --- lib/Makefile | 6 +- lib/hash-istring.c | 20 ++ lib/hash-string.c | 19 ++ lib/hash-test.c | 245 +++++++++++++++++++++ lib/hashfunc.h | 14 ++ lib/hashtable.h | 528 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 830 insertions(+), 2 deletions(-) create mode 100644 lib/hash-istring.c create mode 100644 lib/hash-string.c create mode 100644 lib/hash-test.c create mode 100644 lib/hashfunc.h create mode 100644 lib/hashtable.h diff --git a/lib/Makefile b/lib/Makefile index 2ccb3d0a..2c93e1cf 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -1,4 +1,4 @@ -# Makefile for the Sherlock Library (c) 1997--2001 Martin Mares +# Makefile for the Sherlock Library (c) 1997--2002 Martin Mares DIRS+=lib PROGS+=obj/lib/db-tool obj/lib/buckettool @@ -7,7 +7,8 @@ SHLIB_OBJS=alloc.o alloc_str.o ctmatch.o db.o fastbuf.o fb-file.o fb-mem.o lists log.o log2.o md5.o md5hex.o mmap.o pagecache.o patimatch.o patmatch.o pool.o \ prime.o random.o realloc.o regex.o timer.o url.o wildmatch.o \ wordsplit.o str_ctype.o str_upper.o bucket.o conf.o object.o sorter.o \ - finger.o proctitle.o ipaccess.o profile.o bitsig.o randomkey.o + finger.o proctitle.o ipaccess.o profile.o bitsig.o randomkey.o \ + hash-string.o hash-istring.o obj/lib/libsh.a: $(addprefix obj/lib/,$(SHLIB_OBJS)) @@ -18,3 +19,4 @@ obj/lib/conf-test: obj/lib/conf-test.o obj/lib/libsh.a obj/lib/sort-test: obj/lib/sort-test.o obj/lib/libsh.a obj/lib/lfs-test: obj/lib/lfs-test.o obj/lib/libsh.a obj/lib/regex-test: obj/lib/regex-test.o obj/lib/libsh.a +obj/lib/hash-test: obj/lib/hash-test.o obj/lib/libsh.a diff --git a/lib/hash-istring.c b/lib/hash-istring.c new file mode 100644 index 00000000..e95de44d --- /dev/null +++ b/lib/hash-istring.c @@ -0,0 +1,20 @@ +/* + * Sherlock Library -- Case-Insensitive String Hash Function + * + * (c) 2002 Martin Mares + */ + +#include "lib/lib.h" +#include "lib/hashfunc.h" +#include "lib/chartype.h" + +#include + +uns +hash_string_nocase(byte *k) +{ + uns h = strlen(k); + while (*k) + h = h*37 + Cupcase(*k++); + return h; +} diff --git a/lib/hash-string.c b/lib/hash-string.c new file mode 100644 index 00000000..00623ec0 --- /dev/null +++ b/lib/hash-string.c @@ -0,0 +1,19 @@ +/* + * Sherlock Library -- String Hash Function + * + * (c) 2002 Martin Mares + */ + +#include "lib/lib.h" +#include "lib/hashfunc.h" + +#include + +uns +hash_string(byte *k) +{ + uns h = strlen(k); + while (*k) + h = h*37 + *k++; + return h; +} diff --git a/lib/hash-test.c b/lib/hash-test.c new file mode 100644 index 00000000..c03d9fa4 --- /dev/null +++ b/lib/hash-test.c @@ -0,0 +1,245 @@ +/* Tests for hash table routines */ + +#include "lib/lib.h" + +#include +#include + +#if 1 + +/* TEST 1: integers */ + +struct node { + int key; + int data; +}; + +#define HASH_NODE struct node +#define HASH_PREFIX(x) test_##x +#define HASH_KEY_ATOMIC key +#define HASH_ATOMIC_TYPE int + +#define HASH_GIVE_INIT_DATA +static inline void test_init_data(struct node *n) +{ + n->data = n->key + 123; +} + +#define HASH_WANT_FIND +//#define HASH_WANT_NEW +#define HASH_WANT_LOOKUP +//#define HASH_WANT_DELETE +#define HASH_WANT_REMOVE + +#include "lib/hashtable.h" + +static void test(void) +{ + int i; + + test_init(); + for (i=0; i<1024; i++) + { + struct node *n = test_lookup(i); + ASSERT(n->data == i+123); + } + for (i=1; i<1024; i+=2) + { +#if 0 + test_delete(i); +#else + struct node *n = test_lookup(i); + test_remove(n); +#endif + } + for (i=0; i<1024; i++) + { + struct node *n = test_find(i); + if (!n != (i&1) || (n && n->data != i+123)) + die("Inconsistency at i=%d", i); + } + i=0; + HASH_FOR_ALL(test, n) + { + i += 1 + 0*n->key; + // printf("%d\n", n->key); + } + HASH_END_FOR; + ASSERT(i == 512); + log(L_INFO, "OK"); +} + +#elif 0 + +/* TEST 2: external strings */ + +struct node { + char *key; + int data; +}; + +#define HASH_NODE struct node +#define HASH_PREFIX(x) test_##x +#define HASH_KEY_STRING key +#define HASH_NOCASE + +#define HASH_WANT_FIND +#define HASH_WANT_NEW + +#include "lib/hashtable.h" + +static void test(void) +{ + int i; + + test_init(); + for (i=0; i<1024; i+=2) + { + char x[32]; + sprintf(x, "abc%d", i); + test_new(stralloc(x)); + } + for (i=0; i<1024; i++) + { + char x[32]; + struct node *n; + sprintf(x, "ABC%d", i); + n = test_find(x); + if (!n != (i&1)) + die("Inconsistency at i=%d", i); + } + log(L_INFO, "OK"); +} + +#elif 0 + +/* TEST 3: internal strings + pools */ + +#include "lib/pools.h" + +static struct mempool *pool; + +struct node { + int data; + char key[1]; +}; + +#define HASH_NODE struct node +#define HASH_PREFIX(x) test_##x +#define HASH_KEY_ENDSTRING key + +#define HASH_WANT_FIND +#define HASH_WANT_NEW + +#define HASH_USE_POOL pool + +#include "lib/hashtable.h" + +static void test(void) +{ + int i; + + pool = mp_new(16384); + test_init(); + for (i=0; i<1024; i+=2) + { + char x[32]; + sprintf(x, "abc%d", i); + test_new(x); + } + for (i=0; i<1024; i++) + { + char x[32]; + struct node *n; + sprintf(x, "abc%d", i); + n = test_find(x); + if (!n != (i&1)) + die("Inconsistency at i=%d", i); + } + log(L_INFO, "OK"); +} + +#elif 1 + +/* TEST 4: complex keys */ + +#include "lib/hashfunc.h" + +struct node { + int port; + int data; + char host[1]; +}; + +#define HASH_NODE struct node +#define HASH_PREFIX(x) test_##x +#define HASH_KEY_COMPLEX(x) x host, x port +#define HASH_KEY_DECL char *host, int port + +#define HASH_WANT_CLEANUP +#define HASH_WANT_FIND +#define HASH_WANT_NEW +#define HASH_WANT_LOOKUP +#define HASH_WANT_DELETE +#define HASH_WANT_REMOVE + +#define HASH_GIVE_HASHFN +static uns test_hash(char *host, int port) +{ + return hash_string_nocase(host) ^ hash_int(port); +} + +#define HASH_GIVE_EQ +static inline int test_eq(char *host1, int port1, char *host2, int port2) +{ + return !strcasecmp(host1,host2) && port1 == port2; +} + +#define HASH_GIVE_EXTRA_SIZE +static inline uns test_extra_size(char *host, int port UNUSED) +{ + return strlen(host); +} + +#define HASH_GIVE_INIT_KEY +static inline void test_init_key(struct node *n, char *host, int port) +{ + strcpy(n->host, host); + n->port = port; +} + +#include "lib/hashtable.h" + +static void test(void) +{ + int i; + + test_init(); + for (i=0; i<1024; i+=2) + { + char x[32]; + sprintf(x, "abc%d", i); + test_new(x, i%10); + } + for (i=0; i<1024; i++) + { + char x[32]; + struct node *n; + sprintf(x, "ABC%d", i); + n = test_find(x, i%10); + if (!n != (i&1)) + die("Inconsistency at i=%d", i); + } + log(L_INFO, "OK"); + test_cleanup(); + log(L_INFO, "Cleaned up"); +} + +#endif + +int +main(void) +{ + test(); + return 0; +} diff --git a/lib/hashfunc.h b/lib/hashfunc.h new file mode 100644 index 00000000..126b8f24 --- /dev/null +++ b/lib/hashfunc.h @@ -0,0 +1,14 @@ +/* + * Sherlock Library -- Hash Functions + * + * (c) 2002 Martin Mares + */ + +#ifndef _SHERLOCK_HASHFUNC_H +#define _SHERLOCK_HASHFUNC_H + +uns hash_string(byte *x); +uns hash_string_nocase(byte *x); +static inline uns hash_int(uns x) { return 6442450967*x; } + +#endif diff --git a/lib/hashtable.h b/lib/hashtable.h new file mode 100644 index 00000000..f96a07fd --- /dev/null +++ b/lib/hashtable.h @@ -0,0 +1,528 @@ +/* + * Sherlock Library -- Universal Hash Table + * + * (c) 2002 Martin Mares + */ + +/* + * This is not a normal header file, it's a generator of hash tables. + * Each time you include it with parameters set in the corresponding + * preprocessor macros, it generates a hash table with the parameters + * given. + * + * You need to specify: + * + * HASH_NODE data type where a node dwells (usually a struct). + * HASH_PREFIX(x) macro to add a name prefix (used on all global names + * defined by the hash table generator). + * + * Then decide on type of keys: + * + * HASH_KEY_ATOMIC=f use node->f as a key of an atomic type (i.e., + * a type which can be compared using `==') + * HASH_ATOMIC_TYPE (defaults to int). + * | HASH_KEY_STRING=f use node->f as a string key, allocated + * separately from the rest of the node. + * | HASH_KEY_ENDSTRING=f use node->f as a string key, allocated + * automatically at the end of the node struct + * (to be declared as "char f[1]" at the end). + * | HASH_KEY_COMPLEX use a multi-component key; as the name suggests, + * the passing of parameters is a bit complex then. + * The HASH_KEY_COMPLEX(x) macro should expand to + * `x k1, x k2, ... x kn' and you should also define: + * HASH_KEY_DECL declaration of function parameters in which key + * should be passed to all hash table operations. + * That is, `type1 k1, type2 k2, ... typen kn'. + * With complex keys, HASH_GIVE_HASHFN and HASH_GIVE_EQ + * are mandatory. + * + * Then specify what operations you request (all names are automatically + * prefixed by calling HASH_PREFIX): + * + * init() -- initialize the hash table. + * HASH_WANT_CLEANUP cleanup() -- deallocate the hash table. + * HASH_WANT_FIND node *find(key) -- find node with the specified + * key, return NULL if no such node exists. + * HASH_WANT_NEW node *new(key) -- create new node with given key. + * Doesn't check whether it already exists. + * HASH_WANT_LOOKUP node *lookup(key) -- find node with given key, + * if it doesn't exist, create it. Defining + * HASH_GIVE_INIT_DATA is strongly recommended. + * HASH_WANT_DELETE int delete(key) -- delete and deallocate node + * with given key. Returns success. + * HASH_WANT_REMOVE remove(node *) -- delete and deallocate given node. + * + * You can also supply several functions: + * + * HASH_GIVE_HASHFN unsigned int hash(key) -- calculate hash value of key. + * We have sensible default hash functions for strings + * and integers. + * HASH_GIVE_EQ int eq(key1, key2) -- return whether keys are equal. + * By default, we use == for atomic types and either + * strcmp or strcasecmp for strings. + * HASH_GIVE_EXTRA_SIZE int extra_size(key) -- returns how many bytes after the + * node should be allocated for dynamic data. Default=0 + * or length of the string with HASH_KEY_ENDSTRING. + * HASH_GIVE_INIT_KEY void init_key(node *,key) -- initialize key in a newly + * created node. Defaults: assignment for atomic keys + * and static strings, strcpy for end-allocated strings. + * HASH_GIVE_INIT_DATA void init_data(node *) -- initialize data fields in a + * newly created node. Very useful for lookup operations. + * HASH_GIVE_ALLOC void *alloc(unsigned int size) -- allocate space for + * a node. Default is either normal or pooled allocation + * depending on whether we want deletions. + * void free(void *) -- the converse. + * + * ... and a couple of extra parameters: + * + * HASH_NOCASE string comparisons should be case-insensitive. + * HASH_DEFAULT_SIZE=n initially, use hash table of `n' entries. + * The `n' has to be a power of two. + * HASH_CONSERVE_SPACE use as little space as possible. + * HASH_FN_BITS=n The hash function gives only `n' significant bits. + * HASH_ATOMIC_TYPE=t Atomic values are of type `t' instead of int. + * HASH_USE_POOL=pool Allocate all nodes from given mempool. + * Collides with delete/remove functions. + * + * You also get a iterator macro at no extra charge: + * + * HASH_FOR_ALL(hash_prefix, variable) + * { + * // node *variable gets declared automatically + * do_something_with_node(variable); + * // use HASH_BREAK and HASH_CONTINUE instead of break and continue + * // you must not alter contents of the hash table here + * } + * HASH_END_FOR; + * + * Then include and voila, you have a hash table + * suiting all your needs (at least those which you've revealed :) ). + * + * After including this file, all parameter macros are automatically + * undef'd. + */ + +#ifndef _SHERLOCK_HASHFUNC_H +#include "lib/hashfunc.h" +#endif + +#include + +#if !defined(HASH_NODE) || !defined(HASH_PREFIX) +#error Some of the mandatory configuration macros are missing. +#endif + +#define P(x) HASH_PREFIX(x) + +/* Declare buckets and the hash table */ + +typedef HASH_NODE P(node); + +typedef struct P(bucket) { + struct P(bucket) *next; +#ifndef HASH_CONSERVE_SPACE + uns hash; +#endif + P(node) n; +} P(bucket); + +struct P(table) { + uns hash_size; + uns hash_count, hash_max, hash_min, hash_hard_max, hash_mask; + P(bucket) **ht; +} P(table); + +#define T P(table) + +/* Preset parameters */ + +#if defined(HASH_KEY_ATOMIC) + +#define HASH_KEY(x) x HASH_KEY_ATOMIC + +#ifndef HASH_ATOMIC_TYPE +# define HASH_ATOMIC_TYPE int +#endif +#define HASH_KEY_DECL HASH_ATOMIC_TYPE HASH_KEY( ) + +#ifndef HASH_GIVE_HASHFN +# define HASH_GIVE_HASHFN + static inline int P(hash) (HASH_ATOMIC_TYPE x) + { return hash_int(x); } +#endif + +#ifndef HASH_GIVE_EQ +# define HASH_GIVE_EQ + static inline int P(eq) (HASH_ATOMIC_TYPE x, HASH_ATOMIC_TYPE y) + { return x == y; } +#endif + +#ifndef HASH_GIVE_INIT_KEY +# define HASH_GIVE_INIT_KEY + static inline void P(init_key) (P(node) *n, HASH_ATOMIC_TYPE k) + { HASH_KEY(n->) = k; } +#endif + +#ifndef HASH_CONSERVE_SPACE +#define HASH_CONSERVE_SPACE +#endif + +#elif defined(HASH_KEY_STRING) || defined(HASH_KEY_ENDSTRING) + +#ifdef HASH_KEY_STRING +# define HASH_KEY(x) x HASH_KEY_STRING +# ifndef HASH_GIVE_INIT_KEY +# define HASH_GIVE_INIT_KEY + static inline void P(init_key) (P(node) *n, char *k) + { HASH_KEY(n->) = k; } +# endif +#else +# define HASH_KEY(x) x HASH_KEY_ENDSTRING +# define HASH_GIVE_EXTRA_SIZE + static inline int P(extra_size) (char *k) + { return strlen(k); } +# ifndef HASH_GIVE_INIT_KEY +# define HASH_GIVE_INIT_KEY + static inline void P(init_key) (P(node) *n, char *k) + { strcpy(HASH_KEY(n->), k); } +# endif +#endif +#define HASH_KEY_DECL char *HASH_KEY( ) + +#ifndef HASH_GIVE_HASHFN +#define HASH_GIVE_HASHFN + static inline uns P(hash) (char *k) + { +# ifdef HASH_NOCASE + return hash_string_nocase(k); +# else + return hash_string(k); +# endif + } +#endif + +#ifndef HASH_GIVE_EQ +# define HASH_GIVE_EQ + static inline int P(eq) (char *x, char *y) + { +# ifdef HASH_NOCASE + return !strcasecmp(x,y); +# else + return !strcmp(x,y); +# endif + } +#endif + +#elif defined(HASH_KEY_COMPLEX) + +#define HASH_KEY(x) HASH_KEY_COMPLEX(x) + +#else +#error You forgot to set the hash key type. +#endif + +/* Defaults for missing parameters */ + +#ifndef HASH_GIVE_HASHFN +#error Unable to determine which hash function to use. +#endif + +#ifndef HASH_GIVE_EQ +#error Unable to determine how to compare two keys. +#endif + +#ifdef HASH_GIVE_EXTRA_SIZE +/* This trickery is needed to avoid `unused parameter' warnings */ +#define HASH_EXTRA_SIZE P(extra_size) +#else +/* + * Beware, C macros are expanded iteratively, not recursively, + * hence we get only a _single_ argument, although the expansion + * of HASH_KEY contains commas. + */ +#define HASH_EXTRA_SIZE(x) 0 +#endif + +#ifndef HASH_GIVE_INIT_KEY +#error Unable to determine how to initialize keys. +#endif + +#ifndef HASH_GIVE_INIT_DATA +static inline void P(init_data) (P(node) *n UNUSED) +{ +} +#endif + +#include + +#ifndef HASH_GIVE_ALLOC +#ifdef HASH_USE_POOL + +static inline void * P(alloc) (unsigned int size) +{ return mp_alloc_fast(HASH_USE_POOL, size); } + +#else + +static inline void * P(alloc) (unsigned int size) +{ return xmalloc(size); } + +static inline void P(free) (void *x) +{ xfree(x); } + +#endif +#endif + +#ifndef HASH_DEFAULT_SIZE +#define HASH_DEFAULT_SIZE 32 +#endif + +#ifndef HASH_FN_BITS +#define HASH_FN_BITS 32 +#endif + +/* Now the operations */ + +static void P(alloc_table) (void) +{ + T.ht = xmalloc(sizeof(void *) * T.hash_size); + bzero(T.ht, sizeof(void *) * T.hash_size); + T.hash_max = T.hash_size * 2; + if (T.hash_max > T.hash_hard_max) + T.hash_max = T.hash_hard_max; + T.hash_min = T.hash_size / 4; + T.hash_mask = T.hash_size - 1; +} + +static void P(init) (void) +{ + T.hash_count = 0; + T.hash_size = HASH_DEFAULT_SIZE; +#if HASH_FN_BITS < 28 + T.hash_hard_max = 1 << HASH_FN_BITS; +#else + T.hash_hard_max = 1 << 28; +#endif + P(alloc_table)(); +} + +#ifdef HASH_WANT_CLEANUP +static void P(cleanup) (void) +{ +#ifndef HASH_USE_POOL + uns i; + P(bucket) *b, *bb; + + for (i=0; inext; + P(free)(b); + } +#endif + xfree(T.ht); +} +#endif + +static inline uns P(bucket_hash) (P(bucket) *b) +{ +#ifdef HASH_CONSERVE_SPACE + return P(hash)(HASH_KEY(b->n.)); +#else + return b->hash; +#endif +} + +static void P(rehash) (uns size) +{ + P(bucket) *b, *nb; + P(bucket) **oldt = T.ht, **newt; + uns oldsize = T.hash_size; + uns i, h; + + // log(L_DEBUG, "Rehashing %d->%d at count %d", oldsize, size, T.hash_count); + T.hash_size = size; + P(alloc_table)(); + newt = T.ht; + for (i=0; inext; + h = P(bucket_hash)(b) & T.hash_mask; + b->next = newt[h]; + newt[h] = b; + b = nb; + } + } + xfree(oldt); +} + +#ifdef HASH_WANT_FIND +static P(node) * P(find) (HASH_KEY_DECL) +{ + uns h0 = P(hash) (HASH_KEY( )); + uns h = h0 & T.hash_mask; + P(bucket) *b; + + for (b=T.ht[h]; b; b=b->next) + { + if ( +#ifndef HASH_CONSERVE_SPACE + b->hash == h0 && +#endif + P(eq)(HASH_KEY( ), HASH_KEY(b->n.))) + return &b->n; + } + return NULL; +} +#endif + +#ifdef HASH_WANT_NEW +static P(node) * P(new) (HASH_KEY_DECL) +{ + uns h0, h; + P(bucket) *b; + + h0 = P(hash) (HASH_KEY( )); + h = h0 & T.hash_mask; + b = P(alloc) (sizeof(struct P(bucket)) + HASH_EXTRA_SIZE(HASH_KEY( ))); + b->next = T.ht[h]; + T.ht[h] = b; +#ifndef HASH_CONSERVE_SPACE + b->hash = h0; +#endif + P(init_key)(&b->n, HASH_KEY( )); + P(init_data)(&b->n); + if (T.hash_count++ >= T.hash_max) + P(rehash)(2*T.hash_size); + return &b->n; +} +#endif + +#ifdef HASH_WANT_LOOKUP +static P(node) * P(lookup) (HASH_KEY_DECL) +{ + uns h0 = P(hash) (HASH_KEY( )); + uns h = h0 & T.hash_mask; + P(bucket) *b; + + for (b=T.ht[h]; b; b=b->next) + { + if ( +#ifndef HASH_CONSERVE_SPACE + b->hash == h0 && +#endif + P(eq)(HASH_KEY( ), HASH_KEY(b->n.))) + return &b->n; + } + + b = P(alloc) (sizeof(struct P(bucket)) + HASH_EXTRA_SIZE(HASH_KEY( ))); + b->next = T.ht[h]; + T.ht[h] = b; +#ifndef HASH_CONSERVE_SPACE + b->hash = h0; +#endif + P(init_key)(&b->n, HASH_KEY( )); + P(init_data)(&b->n); + if (T.hash_count++ >= T.hash_max) + P(rehash)(2*T.hash_size); + return &b->n; +} +#endif + +#ifdef HASH_WANT_DELETE +static int P(delete) (HASH_KEY_DECL) +{ + uns h0 = P(hash) (HASH_KEY( )); + uns h = h0 & T.hash_mask; + P(bucket) *b, **bb; + + for (bb=&T.ht[h]; b=*bb; bb=&b->next) + { + if ( +#ifndef HASH_CONSERVE_SPACE + b->hash == h0 && +#endif + P(eq)(HASH_KEY( ), HASH_KEY(b->n.))) + { + *bb = b->next; + P(free)(b); + if (--T.hash_count < T.hash_min) + P(rehash)(T.hash_size/2); + return 1; + } + } + return 0; +} +#endif + +#ifdef HASH_WANT_REMOVE +static void P(remove) (P(node) *n) +{ + P(bucket) *x = SKIP_BACK(struct P(bucket), n, n); + uns h0 = P(bucket_hash)(x); + uns h = h0 & T.hash_mask; + P(bucket) *b, **bb; + + for (bb=&T.ht[h]; (b=*bb) && b != x; bb=&b->next) + ; + ASSERT(b); + *bb = b->next; + P(free)(b); + if (--T.hash_count < T.hash_min) + P(rehash)(T.hash_size/2); +} +#endif + +/* And the iterator */ + +#ifndef HASH_FOR_ALL + +#define HASH_FOR_ALL(h_px, h_var) \ +do { \ + uns h_slot; \ + struct HASH_GLUE(h_px,bucket) *h_buck; \ + for (h_slot=0; h_slot < HASH_GLUE(h_px,table).hash_size; h_slot++) \ + for (h_buck = HASH_GLUE(h_px,table).ht[h_slot]; h_buck; h_buck = h_buck->next) \ + { \ + HASH_GLUE(h_px,node) *h_var = &h_buck->n; +#define HASH_END_FOR } } while(0) +#define HASH_BREAK +#define HASH_CONTINUE continue +#define HASH_GLUE(x,y) x##_##y + +#endif + +/* Finally, undefine all the parameters */ + +#undef P +#undef T + +#undef HASH_ATOMIC_TYPE +#undef HASH_CONSERVE_SPACE +#undef HASH_DEFAULT_SIZE +#undef HASH_EXTRA_SIZE +#undef HASH_FN_BITS +#undef HASH_GIVE_ALLOC +#undef HASH_GIVE_EQ +#undef HASH_GIVE_EXTRA_SIZE +#undef HASH_GIVE_HASHFN +#undef HASH_GIVE_INIT_DATA +#undef HASH_GIVE_INIT_KEY +#undef HASH_KEY +#undef HASH_KEY_ATOMIC +#undef HASH_KEY_COMPLEX +#undef HASH_KEY_DECL +#undef HASH_KEY_ENDSTRING +#undef HASH_KEY_STRING +#undef HASH_NOCASE +#undef HASH_NODE +#undef HASH_PREFIX +#undef HASH_USE_POOL +#undef HASH_WANT_CLEANUP +#undef HASH_WANT_DELETE +#undef HASH_WANT_FIND +#undef HASH_WANT_LOOKUP +#undef HASH_WANT_NEW +#undef HASH_WANT_REMOVE -- 2.39.2