Collections-C, generic data structures for C
github.com
github.com
It's C.
Why on Earth you'd want to allocate a separate list node for each piece of data when you can embed this node directly into the data and then use container_of or similar offsetof() derivative to get a pointer to the data by a pointer to a list item? Saves you at least sizeof(void*) per item and eliminates a chance of list_add ever failing among other things.
The same goes for custom allocators - why would you drag around pointers to malloc, calloc and free, when all you need is just a pointer to realloc?
This sort of thing. The code is nice, but it's not how one would write a container library after few years of hands-on C experience.
So many programmers don't even consider "overhead" of data when writing things. But, most of the time it doesn't matter. Do you need a list of ten things? Great. Do whatever. Do you need a list of a billion things? Then you need to rethink everything from the bottom up.
when all you need is just a pointer to realloc
Wrong! https://github.com/Tarsnap/libcperciva/commit/cabe5fca76f6c3...
Because they're using managed languages where the amount of overhead per object can't be reduced to zero. And also because, in exchange for that overhead, they get other benefits, like compacting garbage collection. But you don't get this in C.
No, of course not "Wrong!"
If a user of a library wants to supply their own allocator, they must provide a version of realloc that acts as free() for the (p,0) case. That's the semantics the library expects, observe them. Internally the lib may look at whether a custom realloc is set, and fallback to malloc/free if it's not.
It's a good approach when the items may need to be in several lists at once.
> More to the point, a payload is welcome to participate on several lists, but only after defining an internal list_head object for each list. In other words, unlike the payloads you're used to that contain only pure data and could (theoretically) be on an infinite number of lists at once, objects on kernel lists can be only on those lists for which they have an internal set of links. It's an interesting way to lock down what lists you're willing to be a member of.
https://github.com/srdja/Collections-C/blob/master/src/hasht... shows the container is manually configurable. Look at the defines at the bottom. This would mean the container file source-header pair, would have to be duplicated for every type, which is somewhat manageable.
Why isn't this written in the readme?
I'm not sure if all that is worth it.
To make it more user friendly, I've provided (yet more) macros to make defining new types super easy. Using the macros does not necessarily involve adding a separate file, although the code is much cleaner if you do. And I don't have anything against adding extra files. There's almost no cost to me. I often group a couple of vectors or linked lists into a single .c/.h file pair to keep things manageable.
The code bloat you get is on par with using C++ templates. In fact, I'd say that it is probably better because the type safe macro's all devolve to a void* underlying implementation rather than generating separate instances. So it's pretty thin. You can always throw away the standard types list that I provide so that you don't have to pay for anything you don't use. And if you really want to, you can interface to the void* underlying implementation directly, loose the indirection costs, the function pointers and the macro costs. Basically, you can choose which world you want to live in, or live in both at the same time.
The long function names are handled by macros again. And since i used function pointer indirection you almost never see them. The costs with this sort of thing are minimal. A typical invocation looks something like
CH_VECTOR(MYVECCLASS)* myvec = NEW_CH_VECTOR(MYVECCLASS);
myvec->append(myvec,object);
There are certainly tradeoffs, the implementation is not nearly as mature as I'd like and debugging problems inside the containers is a HUGE PITA. But in my experience it lets me kee the benefits of working in C (can pull inside the kernel, can port to different machines, easy control over memory and performance) and gives me the flexibility of doing higher level things when I want.https://en.m.wikipedia.org/wiki/Threading_Building_Blocks
I recommend this book to understand such structures:
https://www.goodreads.com/book/show/14788830-structured-para...
I very much welcome your suggestions on how to make better and more useful. :)
http://git.savannah.gnu.org/cgit/kazlib.git/tree/
http://www.kylheku.com/~kaz/kazlib.html
Used in e2fsck since 2002: http://git.kernel.org/cgit/fs/ext2/e2fsprogs.git/commit/e2fs...
The destroy function in cyrus-imapd has both a destroy that takes a cleanup function for values, and an interator:
https://github.com/brong/cyrus-imapd/blob/master/lib/hash.c
I couldn't see how to do either with this library until reading the source code I see you can set a mem_free at creation instead.
The documentation didn't really describe that, and the example is actively wrong.
I see from the code that there's a way to get the keys, from which you can do an external iterator. It's less efficient, but it works. You wouldn't know that from the documentation though, you have to read the source code.
> HashTableConf htc;
Hold short, that structure is allocated from the stack and not via malloc? I'd expect that one to blow up after the second call to hashtable_new.
edit: seems like htc is immutable and the values copied over in hashtable_new_conf, but that's downright scary in case one forgets this semantic and accidentally uses htc for something.
The main alternative, other than hardcoding an element type, is to use macros and pass the element type in the definition.
Sometimes you just don't have the option of a C++ compiler. Also, and this was a situation I was in recently, I needed a collection in a small area of my C code so it really didn't warrant a toolset change.
typedef struct Strref_t {
char* str;
size_t ref;
} Strref;
There, just pass a pointer of that type around.Does that mean that if you're running as 32bit and want to store a number larger than that (a double or int64_t or so) or a POD that all of these have to be allocated seperately?
Remember, a large proportion of C code is for embedded/real-time systems where the only sensible recovery option is to reset.
Attempting to 'handle' an error manually in every case is simply not possible. Better to reset and come up in a clean state than propagate errors.
Having said that, dynamic memory allocation is itself frowned upon in embedded systems.
If we have a failure during rendezvous with our target, it could be a very bad day.
You always need to be able to recover from a soft reboot, even during maneuvers; you're in a high radiation environment and any passing high energy particle or cosmic ray can trigger this.
Any recommendations for reliable C data structures?
If that situation is not allowed, I would suggest you shouldn't be performing memory allocation (or any other resource allocation) dynamically.
J/K
If you don't want to use this style of programming, for whatever reason, check out sys/queue.h. It's already on your system, if you're using some kind of Unix.
If you don't have full control over when memory is being allocated/reallocated, your system is now non-deterministic.
With pure C, you can know exactly when those few extra instructions for resizing your dynamic array are going to happen.
In any case, while C++ has lots of defects, “loss of control relative to what C gives you” isn't one of them.