Forcing me to define `XXX_IMPL` in a single compilation unit is not a header-only library.
It will simply force me to make a `.c` file for every `.h` and define it there.
Why not simply distribute the .c/.h like imgui does, or lua, or many many others?
I'm already copy/pasting your .h files, I would not mind copy/pasting .c files and adding them to my build system, since that's what I'm gonna do anyway to make sure i do not define `XXX_IMPL` multiple times.
Header-only libraries truly only work in C++ when it's all templates anyway.
> It will simply force me to make a `.c` file for every `.h` and define it there.
You can do that of course but you'd be missing the point of how it is meant to be used. Your project already has at least a single .c file anyway, so why make a separate .c file just so you can put `XXX_IMPL` in there instead of putting it in the .c file that is going to be using the library itself?
- I like my object files (.o) to only contain what they are supposed to contain and nothing else
- I don't want to have to keep track of where did I put the `XXX_IMPL`
No I'm not missing the point of how it is meant to be used, I'm criticizing that use case as a very poor one, I'd even say it's an anti-pattern IMHO.
Right, but those would be your choices, it wouldn't be the library itself forcing you to do that, it would be your personal preferences. That isn't a fault of the library.
By not making a choice, the library is actually making the worst possible decision.
It is bloating my ABI if I'm not careful, and it makes distributing more cumbersome when it could/should be straightforward.
That's just not how you are supposed to distribute a C library, this is a poor design that clearly states "I don't know or don't care how to package my code".
There really is no "correct way" to distribute C libraries and at this point it seems you're nitpicking just to nitpick. Imgui uses the same method, no one ever complained. If you do require obj files to not be "bloated" (why?) you can always do it the C way of having separate C/H files.
There is, just like C has been doing in its 50 years of history, header only libraries are a consequence of new generations pretending C is something like Python.
imgui distribute .c/.h files, not .h files with .c embedded in them.
What I'm criticizing is the extra step of me having to write the .c files anyway to avoid bloating the obj files, which should have been done by the library in the first place.
The library did make a choice - header-only C libraries are a choice. And the "how you are supposed to distribute a C library" is clearly just your own opinion you are trying to pass as objective truth. There are many header-only C libraries out there written by many experienced programmers that contradict your opinions.
TBH this is really just bikeshedding, not real issues. IMO even a tab-vs-spaces argument would have more merit as that has real practical effects (appearance of the code in tools like terminal or sites that have forced tabs like code review sites or github).
There are billions of flies who eat shit. Therefore, shit should be edible?
The "numbers argument" is a fallacious one.
No, "how you are supposed to distribute a C library" is not my opinion, but the experience/observations of the last 50 years of engineering. Why would we even have a .so/.a/.lib/.dll format for libraries?
As I said in a sibling comment, those are not header-only libraries. It's poorly packaged .c/.h in a single file that we happened to name with the .h extension.
It's not a header, it's a header+implementation. And it's bad practice, and any decent C developer will tell you so.
How so? The private implementation functions are usually declared static, only the public API functions are visible to the linker. Some single-file libraries also allow to declare the entire API as static (via a config define) so that none of the library API functions are visible to the linker.
Well, everything is in a header, so 'header only' is sorta correct - but the name is also problematic because that's what 'header only' C++ libraries (with the implementation in inline methods) are often called too, and those do actually suffer from downsides, such as increased compilation time.
A more correct name is 'STB-style single-file libraries' and it's really just a distribution format, nothing to get all worked up about - the differences to a single .h/.c pair are mostly negligible.
A common way to integrate such libraries into a non-trivial project is not to create one .c file for each header, but instead to create one .c file for a whole group of related headers (up to all external single-file dependencies of a project in a single implementation file), this may actually decrease compilation time the same way that unity/jumbo builds do, and since external dependencies don't change, this is also not a problem for incremental builds.
Another advantage: at least the original STB libraries (stb_image.h etc...) are often used in small command line tools, those can simply include the implementation in the "main.c" source, resulting in a project with dependencies that's still compiled as a single source file:
And even if you want to do some kind of single-object build for your utility libraries to speed up linking, you can always include the .c file in some big master .c file.
I know that the header-only C and C++ libraries have been catching on for years now, but I don't feel like the trade-offs are meaningful enough for me personally.
I already do something that some people seem to frown upon, which is ship the exact source code of the dependencies that my projects use when writing in C or C++, thus "pinning" them, so that I know you're able to build out of the box without having to download all n number of dependencies, set up their paths, etc. And also, so I know that the dependencies don't disappear, which for some projects I've worked on, they have. And the entire site they came from.
It's also not how I understand the intention of either language. Implementation in .c and .cpp files, interfaces in headers.
Unfortunately you can't please everyone, but I think your approach is the safer one, and it's ok to put your foot down, it's your project after all. The people who would care so much about external dependencies may not be the ones you want on your side anyways. The only middle ground I can even think of would be something like using git submodule/subtree/subrepo where the user can choose to either not even clone those parts (or somehow exclude them altogether) in order to use the system libs, or be able to update the dependencies themselves if they don't want to stay on your known-working versions.
This idiom used to trigger a panic is interesting. Is it also used in the original GLib?
fprintf(stderr, "BUG: _g_hash_table_find_free_slot: Failed to find an empty slot.");
int *ptr = NULL;
*ptr = 0;
I'd have thought this was somewhat dangerous, as dereferencing a NULL pointer is undefined behaviour, so that the compiler is technically free to eliminate '*ptr = 0' (with the result that the code will continue executing).
Back when GLib was written, compilers didn't do anything fancy with code like this, so this was a completely reliable way to trigger a segmentation violation. As always, even though the standard says you should not do that, people write to the actual implementation.
Nowadays, I would expect the entire branch to be marked unreachable, i.e. the whole block would be deleted and if actually reached, nothing would happen.
FWIW from a quick testing both GCC and Clang generate a segfault with full optimizations as long as this isn't the only code in the program (e.g. in my test i put this in a separate function that printf'd some message before doing the assignment).
Also GCC does generate the segfault even if this is the only code (in main) but Clang writes code that exits the program with some error code instead of segfaulting in all optimization levels except 0 (with -O0 it generates the segfault like GCC).
This is/was sometimes used as a debugger trap, i remember a coworker of mine having a t-shirt once that was like "KEEP CALM AND (void*)0 = (void)0" :-P
This most likely is an unexpected failure. Indicating bug in algorithm or memory corruption. Like "Failed to find empty slot, while logic flow says I should have."
That's definitely the intent of the code, but I don't think it's correct to do it this way, as the compiler can just optimise out the null pointer dereference.
Why instantly crash when out of memory instead of letting caller decide what to do?
Also, some GString methods that have string length argument do not append NUL terminator, assuming that the input string is terminated at length+1. This could lead to fun buffer overrun if the string is to be used by functions expecting C-strings. I guess they are to be used when strlen is known by caller, but nevertheless, some documentation would help.
You're right, calling exit when out of memory is at least controversial if not even bad. I still do it because the goal is to be source compatible with GLib. And this is how GLib handles out of memory, unfortunately.
Thanks for pointing out the null termination issue. This was actually a bug and I just fixed it in g_string_append_len. The other _len methods look fine to me.
Since CLib aims to be source compatible with GLib the documentation for the respective classes of GLib should do it:
I had no idea GLib did that. Their string implementation seems to be the best one to use, too, for those looking for a capacity, length, and data implementation. I'm a bit disappointed to know this now. But good on you for being strictly compatible; I feel like many people would fudge the implementation.
Yeah, the GLib documentation is not very good at pointing this out, unfortunately.
I'm also thinking about maybe creating a slightly incompatible version which might use "c_" as a prefix instead of "g_". There would also need to be some changes to the interface and not only the return values because the methods that do a realloc internally only return a pointer to the object itself (e.g. GString*) for chaining and therefore cannot be used to signal an error condition.
But not sure, yet if this is a good idea or just creates confusion.
For the moment I just want to provide the (in my opinion) by far most important classes of GLib because I always missed something like that in the C standard library. This is also why I chose to implement it header-only. It should be as easy as possbile for people to use them.
The out of memory handling is very unfortunate and you should probably not use this code in the middle of a database transaction or something similar but I think for most user level code and situations where you would handle out of memory with exit anyway it is ok as it is.
When I tried GLib and GTK4 I was surprised how much the former reminded me of Core Foundation and the later Cocoa. Unfortunately, as much as I like C, the GObject system was less pleasant and felt like a poor mans Objective-C. I wonder why the GNOME team, and Linux crew in general, never adopted the language? I understand shunning C++, but Objective-C is C with a thin OO layer.
I don't like C++ but even C++ would be a better choice than the GObject system IMO. GObject is abusing C for what C is not designed for. The result is an inefficient and complex system that makes C++98 look simpler.
This is a derivative so yes, you can (just like you can release derivatives of MIT-licensed code[1] under a license that doesn't let the recipient receive the source code, or conversely under a copyleft license).
The problem is more that "public domain" is a concept that is not universally accepted in a legal sense, examples are Europe and in particular Germany. Case in point, Sqlite had to come up with a license agreement to make it usable for companies in those countries.
Public domain exists everywhere, but the concept of releasing something into the public domain by choice doesn't, so in those jurisdictions something only officially enters the public domain some time after either the author's death or publication.
None of the functions seem to be marked static or inline. Isn't this an instant link error once you include the same header in two different translation units?
Static is probably safer in C (as opposed to C++), as it's valid to have a function with the same name defined as inline within one translation unit and elsewhere with external linkage. The compiler is then free to resolve the relevant function calls to either the inline version or the external version (!)
Unlike typical C++ "header only" libraries, the compilation time increase for STB-style "single-file libraries" is completely negligible because the implementation is also just compiled once for the entire project. Skipping an ifdef-block happens at IO speed, and this is very fast (at least since we left floppy disks behind). We also don't advice people to not comment their code (skipping ifdef is about as fast as skipping comments), or use one-character variable and function names to speed up compilation ;)
Back in 1999, I used to wait 1h for building our CRM server solution from scratch, written in a mix of C and TCL, while taking advantage of ClearMake optimization to share object files across the build machines.
So nope, no FUD, rather production deployment experience.
Naturally if one plans to use C as a scripting language, in tiny projects, maybe it doesn't matter.
No, it really can't. Nothing about C guarantees that the implementation will provide `make` (let alone that it will be POSIX make or BSD make or GNU make). Nor (going back to your original comment: https://news.ycombinator.com/item?id=34244452) is it guaranteed to be hosted on a system with dynamic linking.
The OP library has a design such that the only build system you need is the preprocessor. That is a perfectly valid design decision, especially since this is explicitly for lighter-weight uses where GLib would be excessive. If you don't like it, don't use it.
EDIT: Clang, for example, does not provide a `make` or guarantee a `make` exists. It will happily co-exist with BSD make on FreeBSD, or GNU make on macOS, or no make at all. I assume you concede on pkg-config, etc.
That's not really any different from how this library works. CLib just chooses to avoid the necessity of adding additional makefile definitions at the cost of making the preprocessor fast-forward through a bit more text. As it's a small amount of text anyway, I doubt it makes much difference. (The implementation code only gets parsed once, even though it's scanned multiple times by the preprocessor.) It seems a bit unfair to assume that the author has an "unwillingness to learn how to work with compiler toolchains and package repos" just because they chose to make this tradeoff.
Also, OP referred to 'package repos', which obviously aren't involved in your example.
If the library was instead giving me a .c and a .h, I would simply need to make that CMakeLists.txt, or a Makefile, or whatever build system I use.
Now, the library gives me a .h and a `#define XXX_IMPL`. So to avoid making the mistake of defining XXX_IMPL more than once, I'll have to make a .c myself for each file of the library, and then I still need to write the CMakeLists.txt/Makefile/whatever.
This is just wasting my time for no good reasons.
I linked the imgui repository because that's what they do, they give you the .c/.h and you decide how to build it. You just have to copy/paste, no time wasted.
>So to avoid making the mistake of defining XXX_IMPL more than once, I'll have to make a .c myself for each file of the library
Hmm, I guess I wouldn’t worry about that and I’d just define XXX_IMPL in one of the existing .c files that imports the library. If I make a mistake and define it in multiple .c files then I’ll get an easy-to-interpret compiler error and it will be easy to fix the mistake. I imagine that’s how the author envisions the library being used. But yeah, if you don’t want to do it that way for some reason, then there’s no particular advantage to this style. I’m not sure that I hate it any more than I hate every other hack for distributing C libraries in the absence of a proper module system.
Ah, OK. I was confused how that was supposedly used. I suppose header-only C libs are somewhat a pain, as inline does not work the same way as in C++, and static may cause code bloat when inlining does not happen in multiple TUs.
`#include "foo.c"` would repeat the implementation in multiple object files which is both wasteful and, if not using static, create conflicting symbols.
No, because AIUI, header only libraries don't really have an ABI. They get compiled into the application, as opposed to building a separate library that you linked against.
This one is not really a header only library. It becomes a source file when you define a macro, to compile along your other source files.
If you build a shared library with it, it makes sense to set the default symbol visibility to be hidden, so it wouldn't clash with actual glib pulled in from some other shared library. But you can't easily do the same if you build a static library.
And if you expose any function with GHashTable, GList or GString in its signature, you really want them to be binary compatible with actual Glib. It's not true that header-only libraries don't have ABIs.
It will simply force me to make a `.c` file for every `.h` and define it there.
Why not simply distribute the .c/.h like imgui does, or lua, or many many others? I'm already copy/pasting your .h files, I would not mind copy/pasting .c files and adding them to my build system, since that's what I'm gonna do anyway to make sure i do not define `XXX_IMPL` multiple times.
Header-only libraries truly only work in C++ when it's all templates anyway.