patterncMinor
Simple key-value store in C
Viewed 0 times
simplevaluekeystore
Problem
I needed a simple key-value store. Here's what I came up with.
Usage
Use
Of course you can also create a store on the stack:
Add values to the store or update existing values with
Get values from the store with
If the key isn't found,
These examples use strings, but keys and values are really opaque data pointers (
Questions
I'd like to make the
Other than that, I'm just looking for a general review.
Code
kvs.h
```
#ifndef __KVS_H__
#define __KVS_H__
#ifdef __cplusplus
extern "C" {
#endif
#include
typedef struct {
const void *key;
void *value;
} KVSpair;
typedef struct {
KVSpair *pairs;
size_t length;
} KVSstore;
KVSstore *kvs_create(void);
void kvs_destroy(KVSstore *
Usage
Use
kvs_create and kvs_destroy to create stores and clean them up later:KVSstore *store = kvs_create();
...
kvs_destroy(store);Of course you can also create a store on the stack:
KVSstore s = {0};
KVSstore *store = &s;Add values to the store or update existing values with
kvs_put:const char *nameKey = "name";
const char *jobKey = "job";
kvs_put(store, nameKey, "Bob"); // store->length is now 1
kvs_put(store, jobKey, "Janitor"); // store->length is now 2Get values from the store with
kvs_get:const char *name = kvs_get(store, nameKey); // "Bob"
const char *job = kvs_get(store, jobKey); // "Janitor"If the key isn't found,
kvs_get returns NULL. Setting a value to NULL with kvs_put will remove the key-value pair from the store if the key was found.kvs_put(store, jobKey, NULL); // store->length is now 1These examples use strings, but keys and values are really opaque data pointers (
void *). Care should be taken with string keys, since two otherwise identical strings existing at different memory locations are two different keys.Questions
I'd like to make the
length and pairs fields in KVSstore read-only with const (because any attempt to alter these would amount to user error under the use cases I have in mind for this), but I need some way to make them mutable internally. Casting back and forth between KVSstore and something like KVSmutablestore gets old really fast, and makes the code more difficult to follow. I'm trying to figure out a cleaner way to handle that.Other than that, I'm just looking for a general review.
Code
kvs.h
```
#ifndef __KVS_H__
#define __KVS_H__
#ifdef __cplusplus
extern "C" {
#endif
#include
typedef struct {
const void *key;
void *value;
} KVSpair;
typedef struct {
KVSpair *pairs;
size_t length;
} KVSstore;
KVSstore *kvs_create(void);
void kvs_destroy(KVSstore *
Solution
Answer to the question
You may want to hide the
However, you'll lose the feature of statically allocating
Anyway,
Code review
General comments
The code style is good and consistent. You also paid attention to const correctness and some other details, like protecting the header to be included from C++ code and marking private function as
Interface
The interface is quite simple, but well defined and sufficient. I have just some comments:
The
You have commented that keys are just pointers and using strings as keys might not work as expected (and I believe in most practical applications, it wouldn't work indeed).
It is relatively easy to solve this. Add a comparison function pointer to
Also, I couldn't know how to properly use your data structure by looking solely at the header file, specially to know about the removal operation. Add some comments, everyone that uses your code, including yourself, will thank you for that one day.
Implementation
Cast from
Make it explicit that you are casting the pointer from
On my first reading, I didn't notice the cast and I though you had declared a useless redundant variable.
Memory allocation and reallocation
It is good practice to check for any memory allocation failure and maybe return here a boolean (0 or 1) indicating if the function have succeeded. Then change the callers of
Resizing the array of pairs is not just reallocating memory, but also controlling the value of
Element removal
Your algorithm to remove an item from the array is not very good. Once you find the pair inside the array, you just have to move all following pairs to one position before, you don't need to set the key to NULL and sort it again. Substituting the
Then just resize the array, as you are already doing.
Initialization and destruction
The
You may want to hide the
KVSstore structure: just forward declare it in the header or use another opaque structure with just a void pointing to a real internal representation. Then provide a method size_t kvs_length(KVSstore), so users could know how many elements are stored.However, you'll lose the feature of statically allocating
KVSstore, and I can't think of any good solution to this. Both features (forbidding the user to change length and pairs and letting the user set initial values for length and pairs) are somewhat against each other.Anyway,
kvs_destroy doesn't work with statically allocated stores (see comments below).Code review
General comments
The code style is good and consistent. You also paid attention to const correctness and some other details, like protecting the header to be included from C++ code and marking private function as
static.Interface
The interface is quite simple, but well defined and sufficient. I have just some comments:
The
KVSpair could be a forward declaration.You have commented that keys are just pointers and using strings as keys might not work as expected (and I believe in most practical applications, it wouldn't work indeed).
It is relatively easy to solve this. Add a comparison function pointer to
KVSstore. If this pointer is null, kvs_sort_compare just compare key pointers, otherwise, it passes the keys to the comparison function. The user could then just assign strcmp to that pointer.Also, I couldn't know how to properly use your data structure by looking solely at the header file, specially to know about the removal operation. Add some comments, everyone that uses your code, including yourself, will thank you for that one day.
Implementation
Cast from
void*const KVSpair *pairA = a;
const KVSpair *pairB = b;
const KVSpair *pair = element;
store->pairs = realloc(store->pairs, kvs_pair_size * store->length);
KVSstore *store = malloc(kvs_store_size);Make it explicit that you are casting the pointer from
void* to another type, like:const KVSpair *pairA = (const KVSpair*) a;On my first reading, I didn't notice the cast and I though you had declared a useless redundant variable.
Memory allocation and reallocation
static void kvs_resize_pairs(KVSstore *store) {
...
store->pairs = realloc(store->pairs, kvs_pair_size * store->length);It is good practice to check for any memory allocation failure and maybe return here a boolean (0 or 1) indicating if the function have succeeded. Then change the callers of
kvs_resize_pairs to check the return.Resizing the array of pairs is not just reallocating memory, but also controlling the value of
store->length. Thinking this way, I would pass the new desired length as parameter to kvs_resize_pairs instead of setting it prior to calling the function.Element removal
static void kvs_remove_pair(KVSstore *store, KVSpair *pair)Your algorithm to remove an item from the array is not very good. Once you find the pair inside the array, you just have to move all following pairs to one position before, you don't need to set the key to NULL and sort it again. Substituting the
kvs_sort_pairs(store); by the following should work (not tested):memmove(pair, pair + 1, store->length - (pair - store->pairs) - 1);Then just resize the array, as you are already doing.
Initialization and destruction
KVSstore *kvs_create(void)
void kvs_destroy(KVSstore *store)The
kvs_create dynamically allocates a KVSstore and initializes it. The kvs_destroy destroys and releases it. kvs_create shouldn't be needed when using allocation on stack, but how the user is supposed to destroy it, once kvs_destroy tries to free the pointer?Code Snippets
const KVSpair *pairA = a;
const KVSpair *pairB = b;
const KVSpair *pair = element;
store->pairs = realloc(store->pairs, kvs_pair_size * store->length);
KVSstore *store = malloc(kvs_store_size);const KVSpair *pairA = (const KVSpair*) a;static void kvs_resize_pairs(KVSstore *store) {
...
store->pairs = realloc(store->pairs, kvs_pair_size * store->length);static void kvs_remove_pair(KVSstore *store, KVSpair *pair)memmove(pair, pair + 1, store->length - (pair - store->pairs) - 1);Context
StackExchange Code Review Q#63427, answer score: 6
Revisions (0)
No revisions yet.