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
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`. #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.
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.
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.
Long term technical debt
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.
Header only is nice and easy, dump file in the project, #include and your job is done.
That technique is used in sqlite. 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.
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?
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.t...
It's a symptom of the tooling that this is even a thing.
Shudder
Or even plain old tarball with any sort of build script.
And vcpkg, cargo are already good options for those that need cross-os package manager.
says who? care to elaborate?
It is soon 2020, there is no reasons to use C in new code. Because of security and also convenience.
Maybe it is a nice mental exercise (also try brainfuck and malbolge), but not something for production.
It also defines some buffer size names that are likely also declared in other libraries, like `BUF_SIZE`, and will need to be `#undef`ed.
No chunked support. Well, if ever I use this (and... I do have a pressing use for something like this), that will be a PR I'll send in, probably using async.h[0] to allow handlers to be asynchronous.
Also, it's no fair to compare this server's performance to nginx if this server has no TLS support: you'll have to setup a reverse proxy, and then what will that be?
The lack of URI parsing is not a big deal for me, but it'd be nice.
I like the idea. Keep it up. The "some things" that are missing are really the basics of any HTTP server. Now it's more like something you could build a minimal REST API on. (With the need of a "real" server as proxy)
I've gone as far as compiling Go from my Android phone.
Edit here's someone else running Termux with Go:
http://rafalgolarz.com/blog/2017/01/15/running_golang_on_and...
I would also love if someone wanted to contribute fuzz testing or other static analysis to the project. PRs are welcome.
I thought it was poor form to have a lot of code in headers. This seems like it'd be better served as a small C http library.
It would be better if it was namespace'd in some way, at least by using a common unusual prefix for all of the non-local symbols. It wouldn't be hard to clash with the chosen names.
It is mostly because it makes it harder to reuse compiled objects when you make a change and rebuild. For a drop-in library that you're probably not editing, this shouldn't be an issue.
Also I find no handling of slow connections.
This is definitely a toy.
. C (could have been C++... embedded MCU platform)
. compiles cleanly out of the box
. demo is simple, works out of the box, simple to verify
I contrast this with my experience a few days ago with the "Space Invaders in C" post (too lazy to reference it or find the exact title). I do development on my Mac all the time (command-line, C++, clang/gcc). Tried building Space Invaders. One problem after the next. Gave up after about 20-25 minutes.
Cliché as it is, the importance of the "out of box" experience is so important. Especially for a commercial product, which I realize this isn't.
I then went back to using plain C for awhile, and after the complexity of C++, C was so much fun to use.
Anyway, I enjoyed reading through this C header file, and it took me back. But, I am sticking with Lisp because for me it is such a higher productivity language.
Pointer arithmetic and pointers to ephemeral objects is what sucks.
You can get a 3-for-1 with this kind of library by having a libuv implementation.
Honestly I dislike them because they're a redundant pain in the ass. I tried other solutions before (at one job I had 15 years ago we had automatic header code generation, ooof), and although modules are not that ready for primetime [3] (dependency resolution need to be done by an external program), I think they'll be the bees knees.
[1] https://en.wikipedia.org/wiki/Include_guard
[2] (some people will be quick to note that can be solved by pragmas)
[3] https://vector-of-bool.github.io/2019/01/27/modules-doa.html (but he wrote a follow up: https://vector-of-bool.github.io/2019/03/04/modules-doa-2.ht... )
Trying to get multiple C packages from third parties all working together is rough compared to other languages. Rust has me a little spoiled, I guess.
Meanwhile, in C/C++ world, build systems are a mess. You have so many tools. Some folks just use what IDE provides, e.g. MSVC solutions. Some people use CMake. Some people have their handcrafted Makefile solutions. Sure, it works on their platforms, but it's very hard to make it portable. With Cargo and similar, you just go "cargo get" (don't know the exact command, don't use Rust) and you can expect the packages to download and build as needed.
Header files work. They've worked for many decades. Yes, they require software authors to _do more work_, but they also help to eliminate a lib/ directory with 10,000 interdependent libraries that quickly becomes untrusted and frankly, ridiculous.
I don't care for the deep dependency chains of npm, but integrating C libraries which use a mix of different build systems, and making sure your complete project cross-compiles cleanly with different toolchains for different arches is just irritating and time consuming. I don't think the situation is ideal.
Arguably, you can attest some of those issues to JavaScript’s immense popularity but if even Python can manage it, others can too.
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.
Macros defined the individual functions themselves, in one place, (what are their names, arguments, and which library). A regular #include of these macros provided the declarations to the program. The shared lib wrapper module #defined the definition version of these macros prior to the #include, which expand to the invocation stubs.
It makes sense because the users who maintain it just have one place to add a new function; they don't have to do copy and paste programming to write the invocation wrapper.
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.
// 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.
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.
I know C and C++ since 1992, used them across multiple OSes, hardly seen anything that would motivate the fashion of header only files.
As a library author you basically have to wait for distributions to start shipping it, or make users manually install dependencies (and then make the build work with libraries installed into /usr/local).
That's assuming the distro even does package it up. Because beyond Archlinux packaging, packaging up a DEB is painful. RPM is somewhat less painful, but you still need to contend with N distros. Your lib depends on lets say 4 other libraries, perhaps some of the GTK ecosystem of libraries. Good Luck!
When you go to build C projects, there's usually a long list of instructions, per distro/os to set the environment up correctly.
Compare that to a Rust project where you git clone and cargo build to get going. No instructions needed, no insanely complicated esoteric m4 macro system, or ad-hoc programming language (looking at you CMake), and snowflake package manager needed.
Meson is the only C/C++ build tool I know of that solves the issue of building complex C/C++ programs with dependencies sanely. Especially in the case where both your application and your dependencies are unreleased as of yet, as is the case with Gnome and KDE applications as they are developed.
GCC [0], Clang [1] and others [2] have supported for compiled headers. Cmake also has support for precompiled headers [3].
I would say both the tool and architecture to do that is well supported.
[0] https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
[1] https://clang.llvm.org/docs/PCHInternals.html
[2] https://www.qnx.com/developers/docs/6.3.2/neutrino/utilities... (-pch flag)
[3] https://cmake.org/cmake/help/latest/command/target_precompil...
Header-only is the other extreme: No makefile at all. With package managers like Conan and vcpkg, using a build-system like e.g. CMake, it's possible to have very simple and short project files which are easy to integrate.
In this regard, C and C++ are a bit behind the times compared to other languages.
This is also the approach used by this library, see:
https://github.com/jeremycw/httpserver.h#example
This approach also doesn't have the 'build time problems', since the (expensive to compile) implementation code is only seen once by the compiler (unlike many C++ header-only libs, which rely on inline code)
An additional technical downside in this case is the code complexity in the client code, from the required modal preprocessor flags.
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.
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.
[0] http://www.allaboutsymbian.com/news/item/4192_Raccoon_Apache...
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.
Could, should. IRL Docker became a thing mostly because of the hassle it is to do so in C.
Also, now have to prefix the hell out of it, as even anonymous namespace won't work. And not only that.
The hardness of C is its weakness but also its strength. C programmers at least tend to know basic programming and OS management skills while JS programmers... oh hell I'm happy I got out of the mess that is "modern" frontend development.
If you need a library of a version other than the Debian approved version, you are back to manually downloading source tarballs from sourceforge and figuring out their arcane build system.
Also, apt doesn't let you have two versions of the same library installed. For SOME things, they have more than one version package, but in general, you can't have STABLE_VERSION installed for your day-to-day OS usage and DEVELOPMENT_VERSION installed for your development needs. You only get one or the other (this isn't exclusive of apt - all Linux package managers do this, AFAIK).
Anyway, contrast that situation with pip (python), where you can just grab whatever version you want, have the package manager solve the dependencies for you, and slap it all into a virtualenv where it won't interfere with your system-level libraries. Heck, you can even grab versions straight from git, and it will (try to) solve the dependencies (if any).
It's a WHOLE different level of convenience.
Those obviously don’t solve many of the other issues that a real Package manager would, of course.
phew, that vendor lockin that includes tools to install whatever I want!
That random file you downloaded off the internet was built under a specific set of assumptions - assumptions that only hold true if you are running the specific OS version they were targeting.
IF you download the .deb file for your specific OS, and IF you manually install all missing dependencies, then it works. Otherwise, you are still screwed.
At least you can extract it (IIRC, it's just a zip file anyway). But that's no different from going to sourceforge or github or whatever and getting the source tarball... and we are back where we started.
By the way, I was not complaining about "vendor lock-in". I was complaining about Debian's package management policies and how they can affect your software development process in practice - to make the case that apt is a crap replacement for a proper language/library/development/whatever oriented package management.
Generating rpm or deb files is hardly the end of the world, there are even package generators available.
I've never had an issue with a deb package, but I'll tell you it's one of the reasons I stick with Ubuntu for home.
What you are talking about is not an issue in practice.
// httpserverlib.c
#define HTTPSERVER_IMPL
#include "httpserver.h"
// TODO: look up definition of "compilation unit"Even windows.h redefines min and max - blaming single file libraries is ridiculous.
Using a single .c file that includes the file and defines the IMPL symbol would negate what you are saying anyway and in fact is a common way to use them.
Ok, well we're obviously arguing semantics at this point, then, because in my opinion, moving the IMPL definition into a single .c file doesn't negate the complaint about single-header libraries, it makes it no longer a single-header library. :D