9 ms·
Question: I thought any given C code could go in either headers or c files (or rather, split between headers and c files), and that the difference was only a bu
by _bxg1 7y ago
Question: I thought any given C code could go in either headers or c files (or rather, split between headers and c files), and that the difference was only a build concern. So why wouldn't a given library be available in both forms, unless one of them just makes no sense at all? Put differently: why isn't this just "Minimal library for writing non-blocking HTTP servers in C" which people understand to mean "this might make sense to put in a header"?
- zelly 7y agoShort term convenience Long term technical debt
- noobermin 7y agoHow much of that comparison ignoring the time sink that is setting up linkers and dealing with that whole lotta mess. Header only works for some things (particularly things that require no globals, singletons, etc) and that's a valid concern. Saying header-only = long term technical debt always (or even most of the time) feels like an assertion because I've only heard hypotheticals around why it is bad.
- zelly 7y agoIs it really a time sink? You have to have a build system anyway to link together your project itself, assuming it's bigger than a single-file hello world. One extra line in your build system should be the least time consuming part of adding a new foreign dependency--you still have to vet the code for security and figure out how to use it. Unless you're using C++20 modules, you also have to deal with possibly including the header multiple times (slowing down builds), namespacing, macros potentially defined by the header, or a bunch of external/internal linkage edge cases. You only ever find out about these problems once it's too late to remove that library for a different one.
- doliveira 7y ago> One extra line in your build system should be the least time consuming part of adding a new foreign dependency Could, should. IRL Docker became a thing mostly because of the hassle it is to do so in C.
- malkia 7y agoIt depends. But if that header only library is now in your hands - good luck modifying it without paying the penalty of recompiling anywhere else it's used. Also, now have to prefix the hell out of it, as even anonymous namespace won't work. And not only that.
- pjmlp 7y agoIt feels like more being lazy not to learn the proper way of doing it.
- dkersten 7y agoThe proper way to do it sometimes is horrendously complex or uses a build system different from the one you use. Header only avoids this usually.
- pjmlp 7y agoBuilding a plain .a/.lib or .so/.dll is not horrendously complex.
- noobermin 7y agoPackage management is often hideously complex and a central sink of labor effort in linux distros. Building it for yourself is easy, in practice using dynamically linked libraries are the start of all sorts of troubles.
- pjmlp 7y agoProviding static libraries is also an option. Generating rpm or deb files is hardly the end of the world, there are even package generators available.
- dkersten 7y agoI’ve personally had great difficulty building these for large libraries, especially when they use a build tool different than the one I’m familiar with (I use cmake for C++ these days but I find cmake itself horrendously complex and difficult to understand even after investing significant time into it).
- dkersten 7y agoI just watched a cppcon video where Bjarne himself lamented about the complexity of installing many libraries for use in C++.
- slovenlyrobot 7y ago"header only" is C-speak for "up and running in 15 seconds" more or less. One major concern with integrating a new dependency in C land is how much of a pain in the ass it makes your build, especially considering a large chunk of C projects do not target typical desktop environments. After the first time you lost a day trying to get some batshit custom build tool to emit the correct link flags for your exotic ARM board, "header only" carries a lot of weight. On the intermixing of source and header files, it doesn't quite work the way you mentioned. It's not possible to just cut-paste any old public object definition into a header file without at least applying 'static' to it, and that only works where the object truly is private to each translation unit. It's not so easy e.g. to instantiate a global variable shared across the whole program this way, so 'header only' also implies 'almost certainly free of globals', which is a good sign of hygiene edit: yikes, in this case, the implication is totally invalid
- megous 7y agoWell, you can do: int a; int a; static int b; static int b; And it's a valid way to define a single global/static symbol called `a`/`b`.
- arcticbull 7y agoThey have different semantics. The static one can only be referenced in the current translation unit, whereas the non-static one is a global which can be referenced from any translation unit. The problem is that in the header-only case defining "int a" means you can only import the header from a single source file. With "static int a" the visibility is wrong. And if you did "extern int a" linking would fail because the symbol is never assigned to a translation unit.
- tomjakubowski 7y agoHeader-only libraries sometimes require you to define those externs in exactly one "impl" file, which you compile and link to your artifact. Something like this: // libfoo_impl.c // or in some other translation unit, such as main.c #define LIBFOO_IMPL #include "vendor/libfoo.h" // main.c #include "vendor/libfoo.h" int main() { foo_inc(); printf("%d\n", foo_count); return 0; } // vendor/libfoo.h #pragma once extern int foo_count; void foo_inc() { foo_count += 1; } #ifdef LIBFOO_IMPL int foo_count = 0; #endif main.c and libfoo_impl.c both include libfoo.h, which declares foo_count with the right linkage, but the variable is only defined once, in whichever translation unit defines LIBFOO_IMPL. Occasionally you'll find a header library which supports this _IMPL paradigm as an option to avoid inlining its functions in every translation unit that calls them.
- kazinator 7y agoThis is really two files in one. If you have #define HTTPSERVER_IMPL prior to the inclusion, then the header provides the implementation too, otherwise only the interface. Obviously, you must have this HTTPSERVER_IMPL followed by #include "httpserver.h" in only one file in your program. If someone doesn't like it, they can split the file into two: the header proper and the "impl" part in a .c file. Just look for the part starting with #ifdef HTTPSERVER_IMPL. Take that out through to the closing #ifdef and put it into a httpserver.c file. Then put #include "httserver.h" into the top of that file, and remove all *_IMPL preprocessor cruft from httpserver.c. Now you have a proper single-header, single C file module.
- scythe 7y agoThis type of architecture makes more sense when you use it to let the user give arbitrary names and types to the functions, as with e.g. KHASH_INIT(my_t). This version with #ifdef seems a little cargo-culty since the result isn’t as polymorphic as you’d get with klib (which afaict kicked off the header-only craze) so the advantage over a traditional two-file setup is not obvious. (Of course, I don’t know why you need more polymorphism in a small http server!) As noted above, it avoids version incompatibility by essentially forcing static linking. But most developers would statically link a small two-file library anyway, so it’s a moot point. C isn’t supposed to have training wheels.
- namibj 7y ago>C isn’t supposed to have training wheels. And then there came Arduino. We really need to fix the lack of documentation / warnings for these generic C libraries. If you need to understand token-combining preprocessor magic (looking at you, kbtree) due to a lack of proper documentation, these newbies _will_ just try to wing it and _will_ stop poking as soon as it seems to work, regardless of why/how. Say hello to use-after-free & it's cousin, memory leaks.
- pjmlp 7y agoThankfully Arduino uses C++, there are no training wheels beyond taking advantage of C++ improvements over C, and a beginners friendly IDE.
- mnutt 7y agoTo elaborate on the other responses, “single-header” means “one-line import into your project”, and since c doesn’t have a standard module system, the alternative means hooking up the library’s module system to yours, or otherwise separately building and linking it to your project.
- danellis 7y ago> the alternative means hooking up the library’s module system to yours What "module system"? The alternative would be one .c file and one .h file. It's negligible effort to add these to any project that already has more than one .c file.
- mnutt 7y agoI meant build system, such as cmake. I assume the alternative to a single-header library would be lots of .c and .h files, and something to build them into a dynamically-linked library. The alternative, just dumping them into your project, means that once you get into .c files you typically have to start enumerating those somewhere in your own build system, rather than just including them from one of your own headers.
- vnorilo 7y agoBesides, even for a single translation unit (such as a quick hello world) you could just #include the .c file to avoid setting up a makefile.
- ddevault 7y agoYou're right. "Header only" libraries are an anti-pattern in C and the fastest way for me to close the tab when considering a new library. Just put it in two files and it's still dead easy to incorporate into a project.
- blondin 7y ago> "Header only" libraries are an anti-pattern in C says who? care to elaborate?
- hamburglar 7y agoSeparating the interface from the implementation details? Isolating the server code from the module that uses it? Because the implementation is in the header, including it defines a bunch of macros that could easily collide with macros in the larger project. Not knocking the overall effort here, but yes, "header only" libs are definitely an anti-pattern in real world C. I assume the author considers it a curiosity, a novelty, or something that would only be used in very limited circumstances that make such a design an advantage.
- tom_ 7y agoOnly one inclusion expands to produce the implementation.
- hamburglar 7y agoStill suffers from the isolation problem. For example, if you happen to define HTTP_BODY in your own code, it segfaults. This can be avoided by being careful to only define HTTP_BODY after including httpserver.h, but avoiding this type of thing to worry about is the entire point of interface/implementation separation.
- jeremycw 7y agoThis only causes a problem if you define HTTP_BODY in the same file that you define HTTPSERVER_IMPL when including httpserver.h
- bfrog 7y agoC/C++ lack the niceties of npm/cargo/pip/etc, so headers only is like wget something.h and gcc mystuff.c, viola magic. It's a symptom of the tooling that this is even a thing.
- zelly 7y agonpm for C is apt/brew
- Matthias247 7y agoNot really, because the amount of provided package versions is very limited. You won't get an update unless the package maintainer (who is likely not the library owner) provides an update.
- de_watcher 7y agohttp://kmkeen.com/maintainers-matter/ http://kmkeen.com/maintainers-matter/
- eeZah7Ux 7y agoThat's a feature, not a bug. I want to rely on well tested, stable, mature libraries that receive security updates and don't change for the next 3 to 5 years.
- rootlocus 7y agonpm for js is apt/brew for os
- smartmic 7y agoThere are mature solutions for building C/C++ programs which check for dependencies and provide a high quality configuration setup. Think of GNU Autotools. It is often blamed to be too complex, but this complexity is essential, not accidental. You do not want to abstract away details a C/C++ maintainer cares about.
- 7y ago
- duelingjello 7y agoIn a C compiler (cc command), there are only compilation units (.c -> .o). Headers (.h) were bolted-on (cpp command) to deduplicate interfaces (forward declarations). This header-only containing implementations adds its code to every compilation unit and the linker has to be smart enough to deduplicate identical symbols across many compilation units. Linkers (ld command) are provided either by the platform vendor or the compiler vendor, so they may or may not be able to do this. Binutils on Linux is able to do so IIRC. It's a terrible practice that promotes cowboy coding. It should just use two files, which would cause less problems in the real world. You can also write C without headers or newlines. IOCCC may welcome it but why would you ship it or encourage people to use it?
- cellularmitosis 7y ago> This header-only containing implementations adds its code to every compilation unit and the linker has to be smart enough to deduplicate identical symbols across many compilation units. That's not how this works. It's actually a pretty clever trick, and it effectively behaves as a .h / .c pair of files. See https://github.com/nothings/stb/blob/master/docs/stb_howto.txt https://github.com/nothings/stb/blob/master/docs/stb_howto.t...
- ninkendo 7y ago“Clever trick” should be treated as a pejorative. Knowing why I have to `#define FOO_IMPL` before I include the header, but only from one of my compilation units, is a “clever trick” that now anybody reading or maintaining my code has to tuck away into their brain as well. Along with all the other tricks they’ve had to memorize because some other asshole thought they were being clever too.
- wbl 7y agoThis is not the case due to the single definition rule.
- choppaface 7y agoAnother key use case for header-only libs is for games and projects where you need to do rapid prototyping. A lot of game utilities are distributed this way, e.g. https://github.com/RandyGaul/cute_headers https://github.com/RandyGaul/cute_headers
- pjmlp 7y agoEven though I dislike C, I have my share of years coding in C, from all the issues I have with it, lack of rapid prototype was never one of them.
- scraft 7y agoHeader only fixes the lack of build system/module system as part of the language. Some popular build systems include: make, cmake, Visual Studio, Xcode project. Each has a different format for specifying source/header/libraries - and even if you provide project files for all of the above, then what about the other 1,000 build systems? Header only is nice and easy, dump file in the project, #include and your job is done.
- GuB-42 7y agoBeside build consideration, you get a bit more performance out of headers-only libraries and single, large c files. That technique is used in sqlite. https://www.sqlite.org/amalgamation.html https://www.sqlite.org/amalgamation.html > Combining all the code for SQLite into one big file makes SQLite easier to deploy — there is just one file to keep track of. And because all code is in a single translation unit, compilers can do better inter-procedure optimization resulting in machine code that is between 5% and 10% faster.