v1.3.0
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
benchmark
|
||||
hm_test
|
||||
|
||||
16
Makefile
16
Makefile
@@ -1,8 +1,18 @@
|
||||
CC=cc
|
||||
CFLAGS=-std=c11 -Wall -Wextra
|
||||
CXX=g++
|
||||
CXXFLAGS=-Wall -Wextra -O3
|
||||
CXXFLAGS=-std=c++11 -Wall -Wextra -O3
|
||||
|
||||
benchmark: benchmark.cpp
|
||||
benchmark: benchmark.cpp hm.h
|
||||
$(CXX) $(CXXFLAGS) -o benchmark benchmark.cpp
|
||||
|
||||
hm_test: test.c hm.h
|
||||
$(CC) $(CFLAGS) -o hm_test test.c
|
||||
|
||||
test: hm_test
|
||||
./hm_test
|
||||
|
||||
clean:
|
||||
rm -rf benchmark
|
||||
rm -f benchmark hm_test
|
||||
|
||||
.PHONY: test clean
|
||||
|
||||
35
README.md
35
README.md
@@ -1,3 +1,36 @@
|
||||
# hm.h
|
||||
|
||||
A single header library that implements chained hash table in C.
|
||||
A single header, C11 compliant, library that implements a
|
||||
[hash table](https://en.wikipedia.org/wiki/Hash_table#Separate_chaining)
|
||||
in C using separate chaining.
|
||||
|
||||
## Example
|
||||
|
||||
```c
|
||||
#define HM_IMPLEMENTATION
|
||||
#include "hm.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
HashMap ages = {0}; // or hm_create(...) to specify custom key and
|
||||
// value sizes, a size of 0 means a NULL
|
||||
// terminated string
|
||||
|
||||
hm_put(&ages, "alice", "30");
|
||||
hm_put(&ages, "bob", "25");
|
||||
|
||||
char *alice = hm_get(&ages, "alice"); // NULL if key is not found
|
||||
printf("alice is %s\n", alice);
|
||||
|
||||
HashMapIterator it = hm_iterate(&ages);
|
||||
while (hm_next(&it) != NULL) {
|
||||
printf("%s: %s\n", (const char*)hm_key(&it), (char*)hm_value(&it));
|
||||
}
|
||||
|
||||
hm_free(&ages); // The HashMap can still be reused
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
100
benchmark.cpp
100
benchmark.cpp
@@ -6,6 +6,7 @@
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
|
||||
#define HM_IMPLEMENTATION
|
||||
#include "hm.h"
|
||||
@@ -15,16 +16,16 @@ using namespace std::chrono;
|
||||
typedef time_point<high_resolution_clock> time_p;
|
||||
|
||||
// Configuration
|
||||
const int NUM_ELEMENTS = 1000000;
|
||||
const size_t NUM_ELEMENTS = 1000000;
|
||||
|
||||
time_p now()
|
||||
{
|
||||
return high_resolution_clock::now();
|
||||
}
|
||||
|
||||
double time_diff_nano(time_p end, time_p start)
|
||||
double time_diff_ms(time_p end, time_p start)
|
||||
{
|
||||
return duration_cast<nanoseconds>(end - start).count() / 1e6;
|
||||
return duration<double, std::milli>(end - start).count();
|
||||
}
|
||||
|
||||
void print_div()
|
||||
@@ -49,86 +50,85 @@ void print_row(const std::string& label, double hm_ms, double std_ms)
|
||||
}
|
||||
|
||||
// Test with string key type
|
||||
uint64_t hash_str(const void *buffer, size_t size)
|
||||
{
|
||||
(void)size;
|
||||
const char *str = reinterpret_cast<const char*>(buffer);
|
||||
uint64_t hash = (uint64_t)str;
|
||||
return hash;
|
||||
}
|
||||
|
||||
void test_str()
|
||||
{
|
||||
std::vector<std::string> key_pool;
|
||||
std::vector<std::string> value_pool;
|
||||
|
||||
// Generate random strings to ensure we aren't just hashing "1", "2", "3"
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
key_pool.push_back("key_prefix_" + std::to_string(i) + "_suffix_" + std::to_string(rand() % 1000));
|
||||
value_pool.push_back("value_" + std::to_string(i));
|
||||
}
|
||||
|
||||
// --- Custom implementation ---
|
||||
// Using 0 for sizes tells your library to treat them as NULL-terminated strings
|
||||
HashMap hm = hm_create_ex(sizeof(const char *), 0, hash_str);
|
||||
// Using 0 for sizes tells the library to treat them as NULL-terminated strings
|
||||
HashMap hm = hm_create(0, 0);
|
||||
|
||||
// Insertion
|
||||
auto start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
hm_put(&hm, key_pool[i].c_str(), value_pool[i].c_str());
|
||||
}
|
||||
auto end = now();
|
||||
double custom_put = time_diff_nano(end, start);
|
||||
double custom_put = time_diff_ms(end, start);
|
||||
|
||||
if (hm_count(&hm) != NUM_ELEMENTS) {
|
||||
std::cerr << "Error in custom insertion!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Lookup
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
char* val = (char*)hm_get(&hm, key_pool[i].c_str());
|
||||
if (!val) {
|
||||
std::cerr << "Lookup error!" << std::endl;
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
const char* val = (const char*)hm_get(&hm, key_pool[i].c_str());
|
||||
if (!val || strcmp(val, value_pool[i].c_str()) != 0) {
|
||||
std::cerr << "Error in custom lookup!" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
end = now();
|
||||
double custom_get = time_diff_nano(end, start);
|
||||
double custom_get = time_diff_ms(end, start);
|
||||
|
||||
// Deletion
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
hm_remove(&hm, key_pool[i].c_str());
|
||||
}
|
||||
end = now();
|
||||
double custom_del = time_diff_nano(end, start);
|
||||
double custom_del = time_diff_ms(end, start);
|
||||
|
||||
hm_free(&hm);
|
||||
|
||||
// --- Benchmark std::unordered_map ---
|
||||
std::unordered_map<const char*, const char*> std_map;
|
||||
std::unordered_map<std::string, std::string> std_map;
|
||||
|
||||
// Insertion
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
std_map[key_pool[i].c_str()] = value_pool[i].c_str();
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
std_map[key_pool[i]] = value_pool[i];
|
||||
}
|
||||
end = now();
|
||||
double std_put = time_diff_nano(end, start);
|
||||
double std_put = time_diff_ms(end, start);
|
||||
|
||||
// Lookup
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
auto it = std_map.find(key_pool[i].c_str());
|
||||
if (it == std_map.end()) std::cerr << "Lookup error!" << std::endl;
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
auto it = std_map.find(key_pool[i]);
|
||||
if (it == std_map.end() || it->second != value_pool[i]) {
|
||||
std::cerr << "Error in std lookup!" << std::endl;
|
||||
}
|
||||
}
|
||||
end = now();
|
||||
double std_get = time_diff_nano(end, start);
|
||||
double std_get = time_diff_ms(end, start);
|
||||
|
||||
// Deletion
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
std_map.erase(key_pool[i].c_str());
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
std_map.erase(key_pool[i]);
|
||||
}
|
||||
end = now();
|
||||
double std_del = time_diff_nano(end, start);
|
||||
double std_del = time_diff_ms(end, start);
|
||||
|
||||
// Output Results
|
||||
print_head();
|
||||
@@ -143,7 +143,7 @@ void test_str()
|
||||
uint64_t hash_int(const void *buffer, size_t size)
|
||||
{
|
||||
(void)size;
|
||||
return *reinterpret_cast<const int*>(buffer);
|
||||
return *reinterpret_cast<const unsigned int*>(buffer);
|
||||
}
|
||||
|
||||
void test_int()
|
||||
@@ -152,9 +152,9 @@ void test_int()
|
||||
std::vector<int> keys(NUM_ELEMENTS);
|
||||
std::vector<int> values(NUM_ELEMENTS);
|
||||
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
keys[i] = i;
|
||||
values[i] = i * 2;
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
keys[i] = static_cast<int>(i);
|
||||
values[i] = static_cast<int>(i * 2);
|
||||
}
|
||||
|
||||
// --- Custom implementation ---
|
||||
@@ -162,15 +162,15 @@ void test_int()
|
||||
|
||||
// Insertion
|
||||
auto start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
hm_put(&hm, &keys[i], &values[i]);
|
||||
}
|
||||
auto end = now();
|
||||
double custom_put = time_diff_nano(end, start);
|
||||
double custom_put = time_diff_ms(end, start);
|
||||
|
||||
// Lookup
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
int* val = (int*)hm_get(&hm, &keys[i]);
|
||||
if (!val || *val != values[i]) {
|
||||
std::cerr << "Error in custom lookup!" << std::endl;
|
||||
@@ -178,15 +178,15 @@ void test_int()
|
||||
}
|
||||
}
|
||||
end = now();
|
||||
double custom_get = time_diff_nano(end, start);
|
||||
double custom_get = time_diff_ms(end, start);
|
||||
|
||||
// Deletion
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
hm_remove(&hm, &keys[i]);
|
||||
}
|
||||
end = now();
|
||||
double custom_del = time_diff_nano(end, start);
|
||||
double custom_del = time_diff_ms(end, start);
|
||||
|
||||
hm_free(&hm);
|
||||
|
||||
@@ -195,30 +195,30 @@ void test_int()
|
||||
|
||||
// Insertion
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
std_map[keys[i]] = values[i];
|
||||
}
|
||||
end = now();
|
||||
double std_put = time_diff_nano(end, start);
|
||||
double std_put = time_diff_ms(end, start);
|
||||
|
||||
// Lookup
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
auto it = std_map.find(keys[i]);
|
||||
if (it == std_map.end() || it->second != values[i]) {
|
||||
std::cerr << "Error in std lookup!" << std::endl;
|
||||
}
|
||||
}
|
||||
end = now();
|
||||
double std_get = time_diff_nano(end, start);
|
||||
double std_get = time_diff_ms(end, start);
|
||||
|
||||
// Deletion
|
||||
start = now();
|
||||
for (int i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
for (size_t i = 0; i < NUM_ELEMENTS; ++i) {
|
||||
std_map.erase(keys[i]);
|
||||
}
|
||||
end = now();
|
||||
double std_del = time_diff_nano(end, start);
|
||||
double std_del = time_diff_ms(end, start);
|
||||
|
||||
// Output Results
|
||||
print_head();
|
||||
|
||||
329
hm.h
329
hm.h
@@ -1,10 +1,54 @@
|
||||
// hm.h - v1.2.0 - MIT License
|
||||
// hm.h - v1.3.0 - MIT License
|
||||
// chained hash table implementation as a single header library.
|
||||
//
|
||||
// [License and changelog]
|
||||
//
|
||||
// See end of file.
|
||||
//
|
||||
// [About this library]
|
||||
//
|
||||
// This is a header only, C11 compliant, library that implements a hash table
|
||||
// with separate chaining.
|
||||
//
|
||||
// In this library, every slot of a HashMap holds a doubly linked list of
|
||||
// buckets. Each bucket stores its key and value in a single block of memory,
|
||||
// so keys and values are copied into the HashMap. They can either be fixed
|
||||
// size buffers or NULL terminated strings. When the HashMap exceeds its
|
||||
// maximum load factor, it grows and every bucket is moved to a new slot
|
||||
// without copying the stored data again.
|
||||
//
|
||||
// This library also exposes an iterator to walk through every entry of a
|
||||
// HashMap, and compile-time options to customize the hash function,
|
||||
// allocation and growth behavior.
|
||||
//
|
||||
// [Quick start example]
|
||||
//
|
||||
// #define HM_IMPLEMENTATION
|
||||
// #include "hm.h"
|
||||
//
|
||||
// #include <stdio.h>
|
||||
//
|
||||
// int main(void)
|
||||
// {
|
||||
// HashMap ages = {0}; // or hm_create(...) to specify custom key and
|
||||
// // value sizes, a size of 0 means a NULL
|
||||
// // terminated string
|
||||
//
|
||||
// hm_put(&ages, "alice", "30");
|
||||
// hm_put(&ages, "bob", "25");
|
||||
//
|
||||
// char *alice = hm_get(&ages, "alice"); // NULL if key is not found
|
||||
// printf("alice is %s\n", alice);
|
||||
//
|
||||
// HashMapIterator it = hm_iterate(&ages);
|
||||
// while (hm_next(&it) != NULL) {
|
||||
// printf("%s: %s\n", (const char*)hm_key(&it), (char*)hm_value(&it));
|
||||
// }
|
||||
//
|
||||
// hm_free(&ages); // The HashMap can still be reused
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// [Single header library usage]
|
||||
//
|
||||
// Including this header file as such does what you normally expect.
|
||||
@@ -24,12 +68,12 @@
|
||||
// #define HM_INITIAL_CAPACITY new_initial_capacity (256)
|
||||
//
|
||||
// This macro defines the initial amount of buckets a HashMap will
|
||||
// have when allocated.
|
||||
// have when allocated. It must be a power of two.
|
||||
//
|
||||
// #define HM_DEFAULT_HASH_FUNCTION new_hash_function (hm__fnv1a)
|
||||
//
|
||||
// This macro defines the hash function that will be used by default
|
||||
// by every new HashMap except the ones (created with 'hm_create_ex').
|
||||
// by every new HashMap except the ones created with 'hm_create_ex'.
|
||||
// The specified function has to respect the HmHashFunc type
|
||||
// definition signature.
|
||||
//
|
||||
@@ -41,7 +85,16 @@
|
||||
// #define HM_GROWTH_FACTOR new_growth_factor (2)
|
||||
//
|
||||
// This macro defines by how much should a HashMap be reallocated when
|
||||
// rehashing.
|
||||
// rehashing. It must be a power of two greater than 1.
|
||||
//
|
||||
// #define HM_MAX_ALIGNMENT new_max_alignment (alignof(max_align_t))
|
||||
//
|
||||
// This macro defines the maximum alignment that keys and values
|
||||
// stored in a HashMap are guaranteed to have. A fixed-size key or
|
||||
// value is aligned to the largest power of two dividing its size, up
|
||||
// to this limit. It must be a power of two and HM_REALLOC must return
|
||||
// memory aligned to at least this value. The default is the alignment
|
||||
// that realloc() guarantees.
|
||||
//
|
||||
// #define HM_NO_ASSERT
|
||||
//
|
||||
@@ -49,6 +102,11 @@
|
||||
// fail. The disabled assertions will be replaced with a simple
|
||||
// condition that returns false or NULL when they fail.
|
||||
//
|
||||
// #define HM_ASSERT my_assert (assert)
|
||||
//
|
||||
// This macro defines an alternative assertion function. It is ignored
|
||||
// when HM_NO_ASSERT is defined.
|
||||
//
|
||||
// #define HM_REALLOC my_realloc (realloc)
|
||||
// #define HM_FREE my_free (free)
|
||||
//
|
||||
@@ -71,23 +129,31 @@
|
||||
// This functions initializes a HashMap structure with the specified key and
|
||||
// value sizes. If a specified size is 0 then that parameter will be treated
|
||||
// as a NULL terminated buffer for that HashMap. Allocation of the internal
|
||||
// buckets is done only at the first hm_put() call.
|
||||
// buckets is done only at the first hm_put() call. A zero-initialized
|
||||
// HashMap (HashMap hm = {0};) is equivalent to hm_create(0, 0).
|
||||
//
|
||||
// Note: fixed-size keys are hashed and compared byte by byte, so struct keys
|
||||
// with padding must be zero-initialized (e.g. with memset) before
|
||||
// being filled, otherwise equal keys may not be found.
|
||||
//
|
||||
// HashMap hm_create_ex(size_t key_size, size_t value_size, HmHashFunc hash_func)
|
||||
//
|
||||
// This function is the same as hm_create() but with additional parameters
|
||||
// for the configuration of the HashMap.
|
||||
// for the configuration of the HashMap. The hash function receives the key
|
||||
// and its size in bytes. For NULL terminated keys the size excludes the
|
||||
// terminator (it equals strlen(key)).
|
||||
//
|
||||
// void hm_free(HashMap *hm)
|
||||
//
|
||||
// This functions frees and invalidates all the keys and values stored in the
|
||||
// specified HashMap.
|
||||
// specified HashMap. The HashMap keeps its configuration (key size, value
|
||||
// size and hash function) and can be reused afterwards.
|
||||
//
|
||||
// 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. Returns false on error
|
||||
// only if HM_NO_ASSERT is defined.
|
||||
// only if assertions are disabled (HM_NO_ASSERT is defined).
|
||||
//
|
||||
// WARNING: Passing a 'value' pointer that references memory currently stored
|
||||
// within the HashMap (self-referencing update) results in undefined
|
||||
@@ -96,7 +162,8 @@
|
||||
// void *hm_get(const HashMap *hm, const void *key)
|
||||
//
|
||||
// This function returns the value associated with the specified key. Returns
|
||||
// NULL if key was not found.
|
||||
// NULL if key was not found. The returned pointer is suitably aligned for a
|
||||
// value of the HashMap's fixed value size (see HM_MAX_ALIGNMENT).
|
||||
//
|
||||
// type *hm_of(type, hm, key)
|
||||
//
|
||||
@@ -119,6 +186,10 @@
|
||||
// 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.
|
||||
//
|
||||
// WARNING: Calling hm_put(), hm_remove() or hm_free() on a HashMap invalidates
|
||||
// all of its iterators. To remove entries while iterating, collect
|
||||
// their keys first and remove them after the iteration.
|
||||
//
|
||||
// const void *hm_key(const HashMapIterator *it)
|
||||
//
|
||||
// This function returns the key of the current entry. Returns NULL if the
|
||||
@@ -141,6 +212,7 @@
|
||||
#ifndef HM_H_
|
||||
#define HM_H_
|
||||
|
||||
#include <stdalign.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -162,12 +234,17 @@
|
||||
# define HM_GROWTH_FACTOR 2
|
||||
#endif // HM_GROWTH_FACTOR
|
||||
|
||||
#ifndef HM_MAX_ALIGNMENT
|
||||
# define HM_MAX_ALIGNMENT alignof(max_align_t)
|
||||
#endif // HM_MAX_ALIGNMENT
|
||||
|
||||
#ifndef HM_NO_ASSERT
|
||||
# ifndef HM_ASSERT
|
||||
# include <assert.h>
|
||||
# define HM_ASSERT assert
|
||||
# endif // HM_ASSERT
|
||||
#else
|
||||
# undef HM_ASSERT
|
||||
# define HM_ASSERT(...) ((void)0)
|
||||
#endif // HM_NO_ASSERT
|
||||
|
||||
@@ -181,11 +258,8 @@
|
||||
# define HM_FREE free
|
||||
#endif // HM_FREE
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" { // Prevent name mangling
|
||||
#endif // __cplusplus
|
||||
|
||||
// Type definition for a hash function returning a 64-bit hash
|
||||
// Type definition for a hash function returning a 64-bit hash. For NULL
|
||||
// terminated keys 'size' does not include the terminator.
|
||||
typedef uint64_t (*HmHashFunc)(const void *buffer, size_t size);
|
||||
|
||||
typedef struct Hm__Bucket {
|
||||
@@ -194,6 +268,7 @@ typedef struct Hm__Bucket {
|
||||
void *key;
|
||||
void *value;
|
||||
size_t value_size;
|
||||
uint64_t hash;
|
||||
} Hm__Bucket;
|
||||
|
||||
typedef struct {
|
||||
@@ -212,6 +287,10 @@ typedef struct {
|
||||
bool end;
|
||||
} HashMapIterator;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" { // Prevent name mangling
|
||||
#endif // __cplusplus
|
||||
|
||||
// Create and free
|
||||
HashMap hm_create(size_t key_size, size_t value_size);
|
||||
HashMap hm_create_ex(size_t key_size, size_t value_size, HmHashFunc hash_func);
|
||||
@@ -220,7 +299,7 @@ void hm_free(HashMap *hm);
|
||||
// Modify, access and remove
|
||||
bool hm_put(HashMap *hm, const void *key, const void *value);
|
||||
void *hm_get(const HashMap *hm, const void *key);
|
||||
#define hm_of(type, hm, key) (type*)hm_get((hm), (key))
|
||||
#define hm_of(type, hm, key) ((type*)hm_get((hm), (key)))
|
||||
bool hm_remove(HashMap *hm, const void *key);
|
||||
size_t hm_count(const HashMap *hm);
|
||||
|
||||
@@ -230,14 +309,6 @@ const void *hm_key(const HashMapIterator *it);
|
||||
void *hm_value(const HashMapIterator *it);
|
||||
void *hm_next(HashMapIterator *it);
|
||||
|
||||
// Private functions
|
||||
uint64_t hm__fnv1a(const void *buffer, size_t size);
|
||||
size_t hm__key_size(const HashMap *hm, const void *key);
|
||||
size_t hm__value_size(const HashMap *hm, const void *value);
|
||||
bool hm__keycmp(const HashMap *hm, const void *key1, const void *key2);
|
||||
bool hm__rehash(HashMap *hm);
|
||||
Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *value, size_t value_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
@@ -246,9 +317,16 @@ Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *valu
|
||||
|
||||
#ifdef HM_IMPLEMENTATION
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" { // Prevent name mangling
|
||||
#endif // __cplusplus
|
||||
// Private functions
|
||||
uint64_t hm__fnv1a(const void *buffer, size_t size);
|
||||
uint64_t hm__hash(const HashMap *hm, const void *key, size_t key_size);
|
||||
size_t hm__key_size(const HashMap *hm, const void *key);
|
||||
size_t hm__value_size(const HashMap *hm, const void *value);
|
||||
bool hm__keycmp(const HashMap *hm, const void *key1, const void *key2);
|
||||
bool hm__rehash(HashMap *hm);
|
||||
size_t hm__alignment(size_t size);
|
||||
size_t hm__bucket_layout(const HashMap *hm, size_t key_size, size_t value_size, size_t *key_offset, size_t *value_offset);
|
||||
Hm__Bucket *hm__bucket_create(const HashMap *hm, const void *key, size_t key_size, const void *value, size_t value_size, uint64_t hash);
|
||||
|
||||
HashMap hm_create(size_t key_size, size_t value_size)
|
||||
{
|
||||
@@ -280,26 +358,23 @@ void hm_free(HashMap *hm)
|
||||
return;
|
||||
}
|
||||
|
||||
if (hm->map == NULL) {
|
||||
memset(hm, 0, sizeof(*hm));
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < hm->capacity; ++i) {
|
||||
Hm__Bucket *cur = hm->map[i];
|
||||
if (cur == NULL) {
|
||||
continue;
|
||||
if (hm->map != NULL) {
|
||||
for (size_t i = 0; i < hm->capacity; ++i) {
|
||||
Hm__Bucket *cur = hm->map[i];
|
||||
while (cur != NULL) {
|
||||
Hm__Bucket *next = cur->next;
|
||||
HM_FREE(cur);
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
|
||||
while (cur != NULL) {
|
||||
Hm__Bucket *next = cur->next;
|
||||
HM_FREE(cur);
|
||||
cur = next;
|
||||
}
|
||||
HM_FREE(hm->map);
|
||||
}
|
||||
|
||||
HM_FREE(hm->map);
|
||||
memset(hm, 0, sizeof(*hm));
|
||||
// Keep the configuration so that the HashMap can be reused
|
||||
hm->map = NULL;
|
||||
hm->count = 0;
|
||||
hm->capacity = HM_INITIAL_CAPACITY;
|
||||
}
|
||||
|
||||
bool hm_put(HashMap *hm, const void *key, const void *value)
|
||||
@@ -320,24 +395,15 @@ bool hm_put(HashMap *hm, const void *key, const void *value)
|
||||
memset(hm->map, 0, sizeof(*hm->map) * hm->capacity);
|
||||
}
|
||||
|
||||
// Rehash if necessary
|
||||
if ((float)hm->count + 1 > (float)hm->capacity * HM_MAX_LOAD_FACTOR) {
|
||||
if (!hm__rehash(hm)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Key-value sizes
|
||||
size_t key_size = hm__key_size(hm, key);
|
||||
size_t value_size = hm__value_size(hm, value);
|
||||
|
||||
// Get the head bucket
|
||||
size_t idx = hm->hash_func(key, key_size) % hm->capacity;
|
||||
Hm__Bucket *head = hm->map[idx];
|
||||
uint64_t hash = hm__hash(hm, key, key_size);
|
||||
|
||||
// Check if key already exists
|
||||
for (Hm__Bucket *cur = head; cur != NULL; cur = cur->next) {
|
||||
if (!hm__keycmp(hm, key, cur->key)) {
|
||||
Hm__Bucket **slot = &hm->map[(size_t)hash & (hm->capacity - 1)];
|
||||
for (Hm__Bucket *cur = *slot; cur != NULL; cur = cur->next) {
|
||||
if (cur->hash != hash || !hm__keycmp(hm, key, cur->key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -348,7 +414,8 @@ bool hm_put(HashMap *hm, const void *key, const void *value)
|
||||
}
|
||||
|
||||
// Reallocate the entire bucket
|
||||
size_t size = sizeof(Hm__Bucket) + key_size + value_size;
|
||||
size_t key_offset, value_offset;
|
||||
size_t size = hm__bucket_layout(hm, key_size, value_size, &key_offset, &value_offset);
|
||||
Hm__Bucket *new_bucket = (Hm__Bucket*)HM_REALLOC(cur, size);
|
||||
if (new_bucket == NULL) {
|
||||
HM_ASSERT(false && "Reallocation failed");
|
||||
@@ -356,8 +423,8 @@ bool hm_put(HashMap *hm, const void *key, const void *value)
|
||||
}
|
||||
|
||||
// Recalculate key-value pointers
|
||||
new_bucket->key = (uint8_t*)new_bucket + sizeof(*new_bucket);
|
||||
new_bucket->value = (uint8_t*)new_bucket->key + key_size;
|
||||
new_bucket->key = (uint8_t*)new_bucket + key_offset;
|
||||
new_bucket->value = (uint8_t*)new_bucket + value_offset;
|
||||
new_bucket->value_size = value_size;
|
||||
|
||||
// Update linked list pointers of neighbors
|
||||
@@ -365,8 +432,11 @@ bool hm_put(HashMap *hm, const void *key, const void *value)
|
||||
if (new_bucket->prev != NULL) {
|
||||
new_bucket->prev->next = new_bucket;
|
||||
} else {
|
||||
size_t idx = hm->hash_func(key, key_size) % hm->capacity;
|
||||
hm->map[idx] = new_bucket;
|
||||
*slot = new_bucket;
|
||||
}
|
||||
|
||||
if (new_bucket->next != NULL) {
|
||||
new_bucket->next->prev = new_bucket;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,12 +446,22 @@ bool hm_put(HashMap *hm, const void *key, const void *value)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Rehash if necessary, only when a new entry is going to be inserted
|
||||
if ((double)(hm->count + 1) > (double)hm->capacity * (double)HM_MAX_LOAD_FACTOR) {
|
||||
if (!hm__rehash(hm)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the new value
|
||||
Hm__Bucket *new_bucket = hm__bucket_create(key, key_size, value, value_size);
|
||||
Hm__Bucket *new_bucket = hm__bucket_create(hm, key, key_size, value, value_size, hash);
|
||||
if (new_bucket == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t idx = (size_t)hash & (hm->capacity - 1);
|
||||
Hm__Bucket *head = hm->map[idx];
|
||||
|
||||
new_bucket->next = head;
|
||||
if (head != NULL) {
|
||||
head->prev = new_bucket;
|
||||
@@ -404,15 +484,11 @@ void *hm_get(const HashMap *hm, const void *key)
|
||||
}
|
||||
|
||||
size_t key_size = hm__key_size(hm, key);
|
||||
size_t idx = hm->hash_func(key, key_size) % hm->capacity;
|
||||
uint64_t hash = hm__hash(hm, key, key_size);
|
||||
size_t idx = (size_t)hash & (hm->capacity - 1);
|
||||
|
||||
Hm__Bucket *head = hm->map[idx];
|
||||
if (head == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (Hm__Bucket *cur = head; cur != NULL; cur = cur->next) {
|
||||
if (hm__keycmp(hm, key, cur->key)) {
|
||||
for (Hm__Bucket *cur = hm->map[idx]; cur != NULL; cur = cur->next) {
|
||||
if (cur->hash == hash && hm__keycmp(hm, key, cur->key)) {
|
||||
return cur->value;
|
||||
}
|
||||
}
|
||||
@@ -427,19 +503,15 @@ bool hm_remove(HashMap *hm, const void *key)
|
||||
}
|
||||
|
||||
size_t key_size = hm__key_size(hm, key);
|
||||
size_t idx = hm->hash_func(key, key_size) % hm->capacity;
|
||||
uint64_t hash = hm__hash(hm, key, key_size);
|
||||
size_t idx = (size_t)hash & (hm->capacity - 1);
|
||||
|
||||
Hm__Bucket *head = hm->map[idx];
|
||||
if (head == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Hm__Bucket *cur = head; cur != NULL; cur = cur->next) {
|
||||
if (!hm__keycmp(hm, key, cur->key)) {
|
||||
for (Hm__Bucket *cur = hm->map[idx]; cur != NULL; cur = cur->next) {
|
||||
if (cur->hash != hash || !hm__keycmp(hm, key, cur->key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cur == head) {
|
||||
if (cur->prev == NULL) {
|
||||
hm->map[idx] = cur->next;
|
||||
} else {
|
||||
cur->prev->next = cur->next;
|
||||
@@ -532,27 +604,14 @@ void *hm_next(HashMapIterator *it)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (it->bucket != NULL && it->bucket->next != NULL) {
|
||||
it->bucket = it->bucket->next;
|
||||
return it->bucket->value;
|
||||
}
|
||||
|
||||
// Move to next non-empty bucket array slot
|
||||
// Move to next non-empty bucket array slot (or the first one when the
|
||||
// iterator hasn't started yet)
|
||||
while (it->index < hm->capacity) {
|
||||
it->bucket = hm->map[it->index++];
|
||||
if (it->bucket != NULL) {
|
||||
@@ -573,8 +632,6 @@ 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;
|
||||
|
||||
@@ -586,6 +643,21 @@ uint64_t hm__fnv1a(const void *buffer, size_t size)
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint64_t hm__hash(const HashMap *hm, const void *key, size_t key_size)
|
||||
{
|
||||
// NULL terminated keys are hashed without their terminator
|
||||
if (hm->key_size == 0) {
|
||||
key_size -= 1;
|
||||
}
|
||||
|
||||
// A zero-initialized HashMap has no hash function set
|
||||
if (hm->hash_func == NULL) {
|
||||
return HM_DEFAULT_HASH_FUNCTION(key, key_size);
|
||||
}
|
||||
|
||||
return hm->hash_func(key, key_size);
|
||||
}
|
||||
|
||||
size_t hm__key_size(const HashMap *hm, const void *key)
|
||||
{
|
||||
if (hm == NULL || key == NULL) {
|
||||
@@ -641,9 +713,7 @@ bool hm__rehash(HashMap *hm)
|
||||
Hm__Bucket *cur = hm->map[i];
|
||||
while (cur != NULL) {
|
||||
Hm__Bucket *next = cur->next;
|
||||
|
||||
size_t key_size = hm__key_size(hm, cur->key);
|
||||
uint64_t idx = hm->hash_func(cur->key, key_size) % new_capacity;
|
||||
size_t idx = (size_t)cur->hash & (new_capacity - 1);
|
||||
|
||||
// Insert new bucket
|
||||
cur->prev = NULL;
|
||||
@@ -664,16 +734,41 @@ bool hm__rehash(HashMap *hm)
|
||||
return true;
|
||||
}
|
||||
|
||||
Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *value, size_t value_size)
|
||||
size_t hm__alignment(size_t size)
|
||||
{
|
||||
if (key == NULL || value == NULL) {
|
||||
// NULL terminated buffers only need byte alignment
|
||||
if (size == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// A type's alignment always divides its size, so the largest power of
|
||||
// two dividing the size is a safe alignment for it
|
||||
size_t alignment = size & (~size + 1);
|
||||
return (alignment < HM_MAX_ALIGNMENT ? alignment : HM_MAX_ALIGNMENT);
|
||||
}
|
||||
|
||||
size_t hm__bucket_layout(const HashMap *hm, size_t key_size, size_t value_size, size_t *key_offset, size_t *value_offset)
|
||||
{
|
||||
// [ Hm__Bucket ][ padding ][ key ][ padding ][ value ]
|
||||
size_t key_align = hm__alignment(hm->key_size);
|
||||
size_t value_align = hm__alignment(hm->value_size);
|
||||
|
||||
*key_offset = (sizeof(Hm__Bucket) + key_align - 1) & ~(key_align - 1);
|
||||
*value_offset = (*key_offset + key_size + value_align - 1) & ~(value_align - 1);
|
||||
|
||||
return *value_offset + value_size;
|
||||
}
|
||||
|
||||
Hm__Bucket *hm__bucket_create(const HashMap *hm, const void *key, size_t key_size, const void *value, size_t value_size, uint64_t hash)
|
||||
{
|
||||
if (hm == NULL || key == NULL || value == NULL) {
|
||||
HM_ASSERT(false && "Invalid parameters");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Allocate bucket
|
||||
// [ Hm__Bucket ][ key ][ value ]
|
||||
size_t size = sizeof(Hm__Bucket) + key_size + value_size;
|
||||
size_t key_offset, value_offset;
|
||||
size_t size = hm__bucket_layout(hm, key_size, value_size, &key_offset, &value_offset);
|
||||
Hm__Bucket *bucket = (Hm__Bucket*)HM_REALLOC(NULL, size);
|
||||
if (bucket == NULL) {
|
||||
HM_ASSERT(false && "Reallocation failed");
|
||||
@@ -682,9 +777,10 @@ Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *valu
|
||||
memset(bucket, 0, sizeof(*bucket));
|
||||
|
||||
// Prepare key-value pointers offset
|
||||
bucket->key = (uint8_t*)bucket + sizeof(*bucket);
|
||||
bucket->value = (uint8_t*)bucket->key + key_size;
|
||||
bucket->key = (uint8_t*)bucket + key_offset;
|
||||
bucket->value = (uint8_t*)bucket + value_offset;
|
||||
bucket->value_size = value_size;
|
||||
bucket->hash = hash;
|
||||
|
||||
// Copy key-value data
|
||||
memcpy(bucket->key, key, key_size);
|
||||
@@ -693,15 +789,30 @@ Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *valu
|
||||
return bucket;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // HM_IMPLEMENTATION
|
||||
|
||||
/*
|
||||
* Revision history:
|
||||
*
|
||||
* 1.3.0 (2026-10-28) Fix use-after-free when updating a value whose size
|
||||
* changed in a bucket chain;
|
||||
* Fix misaligned keys and values, new HM_MAX_ALIGNMENT
|
||||
* compile-time option;
|
||||
* Fix crash when using a zero-initialized HashMap;
|
||||
* hm_free() now keeps the HashMap configuration so it
|
||||
* can be reused;
|
||||
* Updating an existing key no longer triggers a rehash;
|
||||
* Buckets store their hash for faster lookups and
|
||||
* rehashing;
|
||||
* HM_INITIAL_CAPACITY and HM_GROWTH_FACTOR must now be
|
||||
* powers of two;
|
||||
* Fix hm_of() macro precedence;
|
||||
* The library now targets C11 (or C++11) and
|
||||
* HM_MAX_ALIGNMENT defaults to alignof(max_align_t);
|
||||
* NULL terminated keys are hashed without their
|
||||
* terminator, hash functions now receive strlen(key)
|
||||
* as size for them;
|
||||
* Improved documentation
|
||||
* 1.2.0 (2026-04-08) New HmHashFunc type, configurable by the user with
|
||||
* hm_create_ex() or HM_DEFAULT_HASH_FUNCTION
|
||||
* 1.1.2 (2026-04-08) Optimized Hm__Bucket creation by only allocating one
|
||||
|
||||
268
test.c
Normal file
268
test.c
Normal file
@@ -0,0 +1,268 @@
|
||||
#define HM_IMPLEMENTATION
|
||||
#include "hm.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
fprintf(stderr, "%s:%d: CHECK failed: %s\n", \
|
||||
__FILE__, __LINE__, #cond); \
|
||||
++failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Forces every key into the same chain
|
||||
static uint64_t const_hash(const void *buffer, size_t size)
|
||||
{
|
||||
(void)buffer;
|
||||
(void)size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_int_keys(void)
|
||||
{
|
||||
HashMap hm = hm_create(sizeof(int), sizeof(int));
|
||||
const int n = 10000;
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int v = i * 2;
|
||||
CHECK(hm_put(&hm, &i, &v));
|
||||
}
|
||||
CHECK(hm_count(&hm) == (size_t)n);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int *v = hm_of(int, &hm, &i);
|
||||
CHECK(v != NULL && *v == i * 2);
|
||||
}
|
||||
|
||||
int missing = n;
|
||||
CHECK(hm_get(&hm, &missing) == NULL);
|
||||
|
||||
for (int i = 0; i < n; i += 2) {
|
||||
CHECK(hm_remove(&hm, &i));
|
||||
}
|
||||
CHECK(hm_count(&hm) == (size_t)n / 2);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
CHECK((hm_get(&hm, &i) != NULL) == (i % 2 == 1));
|
||||
}
|
||||
|
||||
hm_free(&hm);
|
||||
}
|
||||
|
||||
static void test_string_value_resize_in_chain(void)
|
||||
{
|
||||
HashMap hm = hm_create_ex(0, 0, const_hash);
|
||||
char big[4096];
|
||||
memset(big, 'y', sizeof(big) - 1);
|
||||
big[sizeof(big) - 1] = '\0';
|
||||
|
||||
// Chain: c -> b -> a
|
||||
CHECK(hm_put(&hm, "a", "x"));
|
||||
CHECK(hm_put(&hm, "b", "x"));
|
||||
CHECK(hm_put(&hm, "c", "x"));
|
||||
|
||||
// Grow every bucket so realloc moves them, then shrink them back
|
||||
CHECK(hm_put(&hm, "a", big));
|
||||
CHECK(hm_put(&hm, "b", big));
|
||||
CHECK(hm_put(&hm, "c", big));
|
||||
CHECK(strcmp((char*)hm_get(&hm, "b"), big) == 0);
|
||||
CHECK(hm_put(&hm, "b", "short"));
|
||||
CHECK(strcmp((char*)hm_get(&hm, "b"), "short") == 0);
|
||||
CHECK(hm_count(&hm) == 3);
|
||||
|
||||
// Removing neighbors walks the prev/next pointers fixed up above
|
||||
CHECK(hm_remove(&hm, "a"));
|
||||
CHECK(hm_remove(&hm, "c"));
|
||||
CHECK(hm_remove(&hm, "b"));
|
||||
CHECK(hm_count(&hm) == 0);
|
||||
|
||||
hm_free(&hm);
|
||||
}
|
||||
|
||||
static void test_alignment(void)
|
||||
{
|
||||
// Variable length string keys followed by doubles
|
||||
HashMap hm = hm_create(0, sizeof(double));
|
||||
const char *keys[] = { "a", "ab", "abc", "abcd", "abcde", "abcdefg" };
|
||||
for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) {
|
||||
double d = (double)i + 0.5;
|
||||
CHECK(hm_put(&hm, keys[i], &d));
|
||||
double *p = hm_of(double, &hm, keys[i]);
|
||||
CHECK(p != NULL && (uintptr_t)p % alignof(double) == 0);
|
||||
CHECK(p != NULL && *p == d);
|
||||
}
|
||||
hm_free(&hm);
|
||||
|
||||
// Odd-sized struct keys followed by struct values
|
||||
typedef struct { int a, b, c; } Key;
|
||||
typedef struct { double x; long long y; } Value;
|
||||
hm = hm_create(sizeof(Key), sizeof(Value));
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
Key k;
|
||||
memset(&k, 0, sizeof(k));
|
||||
k.a = i;
|
||||
Value v = { (double)i, i };
|
||||
CHECK(hm_put(&hm, &k, &v));
|
||||
CHECK((uintptr_t)hm_get(&hm, &k) % alignof(Value) == 0);
|
||||
CHECK((uintptr_t)hm_get(&hm, &k) % alignof(double) == 0);
|
||||
CHECK(hm_of(Value, &hm, &k)->y == i);
|
||||
}
|
||||
HashMapIterator it = hm_iterate(&hm);
|
||||
while (hm_next(&it) != NULL) {
|
||||
CHECK((uintptr_t)hm_key(&it) % alignof(int) == 0);
|
||||
}
|
||||
hm_free(&hm);
|
||||
}
|
||||
|
||||
static void test_zero_initialized(void)
|
||||
{
|
||||
HashMap hm = {0};
|
||||
|
||||
CHECK(hm_get(&hm, "key") == NULL);
|
||||
CHECK(!hm_remove(&hm, "key"));
|
||||
CHECK(hm_put(&hm, "key", "value"));
|
||||
CHECK(strcmp((char*)hm_get(&hm, "key"), "value") == 0);
|
||||
|
||||
hm_free(&hm);
|
||||
}
|
||||
|
||||
static void test_reuse_after_free(void)
|
||||
{
|
||||
HashMap hm = hm_create(sizeof(int), sizeof(int));
|
||||
int k = 1, v = 2;
|
||||
|
||||
CHECK(hm_put(&hm, &k, &v));
|
||||
hm_free(&hm);
|
||||
CHECK(hm_count(&hm) == 0);
|
||||
CHECK(hm_get(&hm, &k) == NULL);
|
||||
|
||||
// Configuration survives hm_free()
|
||||
v = 3;
|
||||
CHECK(hm_put(&hm, &k, &v));
|
||||
CHECK(*hm_of(int, &hm, &k) == 3);
|
||||
|
||||
hm_free(&hm);
|
||||
hm_free(&hm);
|
||||
}
|
||||
|
||||
static void test_iteration(void)
|
||||
{
|
||||
HashMap hm = hm_create(sizeof(int), sizeof(int));
|
||||
const int n = 1000;
|
||||
static bool seen[1000];
|
||||
|
||||
HashMapIterator it = hm_iterate(&hm);
|
||||
CHECK(hm_next(&it) == NULL);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
CHECK(hm_put(&hm, &i, &i));
|
||||
}
|
||||
|
||||
size_t visited = 0;
|
||||
it = hm_iterate(&hm);
|
||||
while (hm_next(&it) != NULL) {
|
||||
int key = *(const int*)hm_key(&it);
|
||||
CHECK(key >= 0 && key < n && !seen[key]);
|
||||
CHECK(*(int*)hm_value(&it) == key);
|
||||
seen[key] = true;
|
||||
++visited;
|
||||
}
|
||||
CHECK(visited == (size_t)n);
|
||||
CHECK(hm_key(&it) == NULL && hm_value(&it) == NULL);
|
||||
|
||||
hm_free(&hm);
|
||||
}
|
||||
|
||||
static void test_update_does_not_rehash(void)
|
||||
{
|
||||
HashMap hm = hm_create(sizeof(int), sizeof(int));
|
||||
const int n = (int)(HM_INITIAL_CAPACITY * HM_MAX_LOAD_FACTOR);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
CHECK(hm_put(&hm, &i, &i));
|
||||
}
|
||||
CHECK(hm.capacity == HM_INITIAL_CAPACITY);
|
||||
|
||||
int k = 0, v = 42;
|
||||
CHECK(hm_put(&hm, &k, &v));
|
||||
CHECK(hm.capacity == HM_INITIAL_CAPACITY);
|
||||
CHECK(*hm_of(int, &hm, &k) == 42);
|
||||
|
||||
hm_free(&hm);
|
||||
}
|
||||
|
||||
static void test_custom_hash(void)
|
||||
{
|
||||
HashMap hm = hm_create_ex(0, sizeof(int), const_hash);
|
||||
char key[16];
|
||||
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
snprintf(key, sizeof(key), "k%d", i);
|
||||
CHECK(hm_put(&hm, key, &i));
|
||||
}
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
snprintf(key, sizeof(key), "k%d", i);
|
||||
CHECK(*hm_of(int, &hm, key) == i);
|
||||
}
|
||||
hm_free(&hm);
|
||||
|
||||
hm = hm_create_ex(0, 0, NULL);
|
||||
CHECK(hm.hash_func == HM_DEFAULT_HASH_FUNCTION);
|
||||
hm_free(&hm);
|
||||
}
|
||||
|
||||
static size_t last_hash_size;
|
||||
|
||||
// Records the size it receives
|
||||
static uint64_t size_hash(const void *buffer, size_t size)
|
||||
{
|
||||
last_hash_size = size;
|
||||
return hm__fnv1a(buffer, size);
|
||||
}
|
||||
|
||||
static void test_hash_size(void)
|
||||
{
|
||||
// NULL terminated keys are hashed without their terminator
|
||||
HashMap hm = hm_create_ex(0, 0, size_hash);
|
||||
CHECK(hm_put(&hm, "abc", "v"));
|
||||
CHECK(last_hash_size == 3);
|
||||
CHECK(hm_get(&hm, "abc") != NULL);
|
||||
CHECK(last_hash_size == 3);
|
||||
CHECK(hm_put(&hm, "", "empty"));
|
||||
CHECK(last_hash_size == 0);
|
||||
CHECK(strcmp((char*)hm_get(&hm, ""), "empty") == 0);
|
||||
CHECK(hm_remove(&hm, ""));
|
||||
CHECK(last_hash_size == 0);
|
||||
hm_free(&hm);
|
||||
|
||||
// Fixed-size keys are hashed with their full size
|
||||
hm = hm_create_ex(sizeof(int), sizeof(int), size_hash);
|
||||
int k = 7;
|
||||
CHECK(hm_put(&hm, &k, &k));
|
||||
CHECK(last_hash_size == sizeof(int));
|
||||
hm_free(&hm);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_int_keys();
|
||||
test_string_value_resize_in_chain();
|
||||
test_alignment();
|
||||
test_zero_initialized();
|
||||
test_reuse_after_free();
|
||||
test_iteration();
|
||||
test_update_does_not_rehash();
|
||||
test_custom_hash();
|
||||
test_hash_size();
|
||||
|
||||
if (failures > 0) {
|
||||
fprintf(stderr, "%d check(s) failed\n", failures);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("All tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user