Show HN: libcodr7 – fundamental collections in the spirit of C
github.com
github.com
The code shown here seems to indicate that insert has at least four cases, and delete has six: https://en.wikipedia.org/wiki/Red%E2%80%93black_tree
But the code I see here is very small: https://github.com/codr7/libcodr7/blob/master/source/codr7/r...
I guess it's not handling delete, which probably accounts for a lot of the difference.
EDIT: I see that it's implementing a left-leaning red/black tree. I see some caution about that approach here? Would love to get the author's opinion about this: http://read.seas.harvard.edu/~kohler/notes/llrb.html
I haven't checked how this relates to the code in this library.
I skimmed the link, but from what I can see there are no issues there that outweigh the significantly simpler code for me. This may very well change once I get more experience with them, time will tell.
khash: https://github.com/attractivechaos/klib/blob/master/khash.h
The interface I really liked, and you can customize the hash equality functions.
rb from jemalloc: https://github.com/jemalloc/jemalloc/blob/dev/include/jemall...
The interface is a bit wacky, but it is flexible enough for my needs. rb shines when you need range queries (keys less than, more than etc.)
[0] - https://apr.apache.org [1] - https://github.com/opensource-apple/CF
To each their own but IMO repositories created in that manner are generally not very useful (though there are exceptions).
The official location of the CoreFoundation (actually CFLite) source dumps is https://opensource.apple.com/tarballs/CF/
The most recent CFLite tarball is https://opensource.apple.com/tarballs/CF/CF-1153.18.tar.gz
You can browse the CFLite source online at the official location as well: https://opensource.apple.com/source/CF/CF-1153.18/
The docs for CoreFoundation are at https://developer.apple.com/documentation/corefoundation
https://github.com/apple/swift-corelibs-foundation/tree/mast...
...as it’s more up-to-date and developed in the open.
I thought there would be a usage example in the tests, but there wasn't.
They have somewhat of an example usage of the list in the `deque.c` file: https://github.com/codr7/libcodr7/blob/2b598e3d89d878ef53e05...
If you don't know about `container_of`, the idea is that it allows you to take a pointer to an inner field, and transform it into a pointer to an outer 'container'. For the linked-list, this means that if you want to make a list of `struct car` objects, you simply embed a `struct c7_list` into `struct car`, pass the address of that internal `struct c7_list` object to the list manipulation functions provided here, and then use `container_of` (Or `c7_baseof`) to transform the pointer to the `struct c7_list` into a pointer to the containing `struct car`.
In general, I think `container_of` is one of the best things for writing C code, and it provides a surprising amount of flexibility and nice patterns you can use. It does have the obvious issue of type checking (You could have a `struct wheel` that also has a `struct c7_list` on it, and accidentally add ti to the same `struct c7_list` that has `struct car`), and if your `struct car` needs to be on more than one list at a time it can get confusing knowing which `struct c7_list` entries belong to which list, but in general I'd call it a big net positive.
It's also possibly worth noting, you can use this to implement a non-intrusive list if you want, just simply make an object that wraps a `struct c7_list` and a void pointer (Or a typed pointer, if you want), and then write some extra logic around it for allocating nodes and such.
And I would agree, I find a surprising number of people think C is 'easy' because it doesn't contain that many built-in constructs, and then get completely stuck when they try to use it. To get really good at C, you need a very solid understanding of common and effective patterns you can use. You can still write C without such things, but it quickly becomes a mess of random patterns and inconsistencies. And unfortunately, I can't really think of one source you can really point to for learning them (though admittedly I haven't really looked into it tons).
These days I definitely prefer embedding/baseof since it's more explicit.
When I need polymorphism, stuffing some function pointers in a struct usually works well enough.
Edit: aha, I see "intrusive" was the keyword I was missing to learn all about this different style.
Not every `container_of` data structure is perfect, but in general I'd say they're just as nice to use as any other language's data structures (Though `container_of` is flexible in different ways). The only big downside is that it not part of the C standard, leading to more than a few implementations, some more featureful than others. This one is probably the "least featureful" version I've seen, which may be intentional. The Linux Kernel's implementation has a lot more utility things like looping in different ways and different types of list manipulations. Some are highly useful, others not at much.
There are less allocations involved (perf and points of failure), values can be inserted on different lists without new allocations, deletion is O(1), elements can be heterogeneous (different sizes and types), etc
etc
I seldomly use linked lists, but most of the time i prefer them to be intrusive. There of course are intrusive list implementations on C++.
Using non-intrusive linked lists just feels wrong once you've gotten used to the idea of embedding links, I find there are always better options.
Intrusive lists on the other hand is simply the best solution in some cases.
Not multiple lists at the same time, surely...?
That said, you can add an entity onto multiple lists if it contains multiple nodes embedded inside. You have to keep track of which nodes are attached to which lists though (Which generally just means being consistent on which you use where). If you give them decent names, then it's not usually a problem, but it can sometimes get a little confusing if you're not careful.
[0] https://github.com/codr7/libcodr7/blob/master/source/codr7/r...
[1] https://github.com/codr7/libcodr7/blob/master/source/codr7/r...
[2] https://github.com/codr7/libcodr7/blob/master/source/codr7/r...
enum c7_order {C7_LT, C7_EQ, C7_GT};
instead of a signed int which seems to be the idiomatic way in C?When you have a finite set of cases, enum is just the right solution period. I have no idea why people still insist on using ints.
(NetBSD has had an all-macro red/black tree implementation forever, plus the ancient and well-understood BSD queue.h)
There's nothing new under the sun, I never claimed novelty. This is simply how I would do it, take or leave.
There's no working examples shown. There's a "real-world examples" link, but it seems to just point to another project of yours.
There's no documentation of the API. If I wanted to use this, I would need to manually read the header files and hope that I'm using them correctly.
And to top it all off, there's not a single comment in the entire project.
I don't mean to be harsh, but if you want other developers to use this, then you need to provide more resources.
It's as real world as it gets for now, the test suite should give you an idea.
I will typically add comments where I feel the code doesn't do a good enough job of explaining itself.
One step at a time.
It's ok, I don't expect anything.
But actually, to me that means a thin, light API with a couple of functions and some macros. Does what it needs to do, no more. Error handling is basic if even provided, usually via return of numerical error codes.
Further, special care is taken to reduce the number of allocations, often using pools/slab allocators. And value semantics, or rather byte array semantics, which means items are treated as blocks of N bytes rather than pointers/references.
This is simply how I would do it based on my experience.
I find chatty generic code where everything is nailed to the floor ugly, Rust, C++, Swift, Java etc. So I guess it depends on who you ask and their experience.
There is nothing wrong with macros, though I definitely prefer the Common Lisp kind.
Some libraries like Cairo seem to be generally known as "Cairo", not "Libcairo". But there is also a trend for libraries to have very straight-to-the-point names. If the PNG parsing library is called libpng.so and the XML parsing library is called libxml.so, it would be confusing to call these libraries just "Png" and "Xml".
As an other example, the two C programs "systemd" and "libsystemd" live in the same repo, but are really two different beasts: libsystemd might be useful to you even on a system which doesn't use systemd.