CLib: Header-only C library that implements the most important classes from GLib
github.com
github.com
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.
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.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".
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).
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.
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.
https://github.com/lieff/minimp3
https://github.com/Immediate-Mode-UI/Nuklear
https://github.com/mattiasgustavsson/libs/blob/main/docs/htt...
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.
Also compilation of a single file is a lot worse when it's really big, compared to several smaller files that can use multiple cores.
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:
cc -O3 mytool.c -o mytool
...e.g. no build system needed at all.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.
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).I think this is unique to CLib, not from GLib.
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.
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).
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.
https://libsoup.org/glib/glib-Memory-Allocation.html#g-mallo...
https://stackoverflow.com/questions/16974254/glib-handle-out...
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:
https://libsoup.org/glib/glib-Strings.html
Thanks for sharing this.
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.
> Copyright 1988-90 by Robert B. Stout dba MicroFirm > * > * Released to public domain, 1991
However the header and readme state MIT license. You can't just release public domain code under a new license right?
[1] "derivatives" include compiled binaries
> Just copy paste the source code in your source tree
And/Or make a simple CMakeLists.txt like this:
cmake_minimum_required(VERSION 3.22.0)
project(mylib C)
file(GLOB_RECURSE MYLIB_SOURCES
${PROJECT_SOURCE_DIR}/src/*.c
)
file(GLOB_RECURSE MYLIB_HEADERS
${PROJECT_SOURCE_DIR}/include/*.h
)
add_library(${PROJECT_NAME} STATIC ${MYLIB_HEADERS} ${MYLIB_SOURCES})
target_include_directories(${PROJECT_NAME}
PRIVATE "${PROJECT_SOURCE_DIR}/include"
)Also, OP referred to 'package repos', which obviously aren't involved in your example.
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.
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.
(and while Dear ImGui is definitely manageable, you still need to decide what to include in your project, for instance: why add imgui_demo.cpp)
If targeting only Linux workloads, use something like checkinstall to generate all lib-xyz-dev flavours.
If targeting cross platform, settle on one of conan, vcpkg or cmake packages.
A "classic C library" cannot assume the existence of pkg-config (or checkinstall, or conan, or vcpkg, or cmake).
It can assume the existence of the preprocessor.
EDIT: Since you modified the comment, it can at very least assume the existence of a compiler, a linker and make.
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.
Another example of lack of understanding how C toolchains work.
Since the days C got outside Bell Labs, every single C compiler has provided those tools.
Ignoring 50 years of C practices in library distribution is exactly that, willful ignorance.
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 ;)
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.
#define XXX_IMPL
#include "xxx.h"
And #include "xxx.c"
Are exactly the same trash.https://www.ibm.com/docs/en/ssw_ibm_i_73/rzarg/inline_linkag...
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.