11 ms·
I really do not understand why 'single header' is considered a good thing, but I see this more and more often on libraries. What is the reason all the code is p
by zevv 9y ago
I really do not understand why 'single header' is considered a good thing, but I see this more and more often on libraries. What is the reason all the code is put in the header file?
- kzrdude 9y agobecause dependency management and build systems are hard to get right and not standardized. It's a simple distribution model that works everywhere.
- badsectoracula 9y agoYes, but why a single header file and not a pair of .c and .h files (personally this is what i do for my small libs)? With a single header file you'd need the user to put it in a specially designated "this is the implementation" .c file anyway. I can see it for C++ which supports placing code in a header as a language, but C requires jumping over awkward preprocessor hoops that can be avoided by using a .c file.
- c-smile 9y agoWith .h only files, when you need to include needed functionality, you modify only your own source file. But with .c files, when you have multiplatform project, you need to put it in other "3rd party" tools. Think about the whole zoo: Visual Studio, XCode, Code::Blocks, make, NMake, etc.
- badsectoracula 9y agoWhy you need to do that with .c files? You can just drop the .c/.h pair into the same place as you would put the single .h file and the rest of your C/C++ files and use it like that.
- satysin 9y agoYou are free to split it into .h and .c if you want, every single header library I have used has an IMPLEMENTATION block to make doing so very simple.
- c-smile 9y agoLet's say you need zip lib functionality in your project. If it will be available as set of .h files you would include it by one line `#include zip.h` in your .c file. But libzip is made of .c files. How you would include it in your multiplatform project ? Visual Studio, XCode, etc... In general it would be great if C has `#include source "some.c"` feature. So you would add single file zip-lib.c that will contain `#include source ...` references to all needed .c files. But C/C++ has no modules as other languages so we need palliatives like make files, etc.
- badsectoracula 9y agoI am not comparing many-c-files (like your libzip example) with single header, i am comparing the single header with a pair of c/h files.
- pjmlp 9y agoEasy, one links to the binary library. It really feels strange that young devs find it so hard to use a linker.
- yoklov 9y agoIt feels strange that you're unfamiliar with the headaches that brings for cross platform projects.
- pjmlp 9y agoThose headers have code that most likely require linking with other libraries, many of each are not available in source code that one can dump into an header file. So it appears like a little convenience just for not having to deal with installing binary libraries, at the expense of wasting everyone's time. The time I waste installing a binary library? Once per platform for the longevity of the project. The time I waste building software with header only library? Every single time I do make all.
- loup-vaillant 9y ago> But with .c files, when you have multiplatform project, you need to put it in other "3rd party" tools. No you don't: just act as if you've written the damn thing yourself. Your tools don't have to know. How being multiplatform changes anything?
- gmueckl 9y agoIt changes everything. Each platform brings its own preferred build system with it, which needs a copy of a definitive and explicit lost of all source files to compile the project. This is cumbersome to maintain. Header files on the other hand are licked up be the compiler automatically after seeing the include statement in the source. So this is actually much less time consuming to maintain. Also, it shows that building C and C++ is a sad affair.
- FRex 9y agoSome libraries do go with a pair because they find the single header too spartan or awkward. Either way works, the C preprocessor and compiler should chew through either style effortlessly and it's trivial to spit the header into a pair or merge a pair into a header by hand if you wish.
- IshKebab 9y agoHeader only means you don't have to modify your build system at all, and C++ build systems nearly all suck. A lot. (Only exceptions I've found are Meson and QBS.) Though I agree a single header and single .c/cpp file is another good option and solves the compile time issues of throwing everything in the header.
- printf_kek0 9y agoI would also like an explanation. The author(s) of Musl (libc alternative) mention on this page that it can potentially invoke undefined behavior: https://wiki.musl-libc.org/alternatives.html https://wiki.musl-libc.org/alternatives.html However, having used a few of these "single-header" libraries my main concern is navigating a 9000 sloc header file as opposed to a neatly re-factored version... An explanation of how undefined behaviour is possible would be welcome.
- to3m 9y agoPerhaps the musl author is referring to Sean Barrett's comments about strict aliasing on his web page: http://nothings.org/ http://nothings.org/ Fun rant by the owner of the company Sean Barrett works for: http://web.archive.org/web/20160309163927/http://robertoconcerto.blogspot.com/2010/10/strict-aliasing.html http://web.archive.org/web/20160309163927/http://robertoconc... (and while we're on the subject, additional UB links: https://news.ycombinator.com/item?id=14170585 https://news.ycombinator.com/item?id=14170585) As for how you navigate these files, you use a text editor that understands that you're working with code and not just a list of chars. Example post about this from last time Nuklear was discussed: https://news.ycombinator.com/item?id=11532001 https://news.ycombinator.com/item?id=11532001 Personally, for Visual Studio I use DPack (http://www.usysware.com/dpack/CodeBrowser.aspx); http://www.usysware.com/dpack/CodeBrowser.aspx); in Xcode, I use its methods dropdown (Ctrl+6); in VSCode, I use its methods dropdown (Ctrl+2); and in Emacs I use a thing I wrote ages ago that grabs the imenu names list and presents it in an ido menu in the minibuffer. And there are other options for other editors. The key thing is just to unshackle yourself from PgUp/PgDn/Find/mouse wheel.
- CamperBob2 9y agoAn explanation of how undefined behaviour is possible would be welcome. Short summary: compilers that enable strict aliasing assumptions by default can introduce bugs into otherwise-working code by assuming that a pointer to one type will never refer to a variable of another type. This assumption is perilous but considered worthwhile by many, since it allows the compiler to take advantage of early-out optimizations and CPU pipeline scheduling in ways that would otherwise be unavailable at compile time. More specifically, compilers may make different decisions about the appropriateness of aliasing optimizations depending on the information they have available. If a function that accepts potentially-aliased pointers exists in its own C file, its visibility to the compiler is limited to what the linker can see. So the compiler may not be as aggressive about its aliasing-safety assumptions as it would be if the function and all of its callers were all present in the same file. Under these conditions, a single-header library can result in "riskier" optimizations that the compiler wouldn't attempt if the same code resided in its own translation unit. My understanding of Sean's position is that this breaches an implicit contract between the programmer and the compiler of a systems-level language. I agree, and I think the standard should have included a keyword -- or the compiler authors a flag -- to allow people to opt in to these sorts of optimizations rather than requiring them to opt out. It is way too late to change the way C works by default by doing stuff like this. This is an oversimplification but I think it's what the Musl author is getting at. My guess is that if he knew Sean, he'd be a lot slower to accuse him of being ignorant of any particular aspect of what he's doing. Still, while his dismissal amounts to FUD in the absence of any specific examples of "undefined behavior," there are some good points on both sides of the argument. My own take, which is unfortunately all too easy to back up with specific historical examples, is that it's inappropriate to do anything that makes C/C++ programming more difficult, more error-prone, or less secure than it already is.
- kilpikaarna 9y agoEspecially when that single header is over 13k lines and reimplements half the standard library. This looks pretty cool and I have an idea for an OpenGL project that needs a GUI, so I'll probably try it out at some point, but it doesn't really seem as lean and straightforward as the blurb implies...
- dangerbird2 9y agoReimplementing parts of the standard library is important for applications where you don't neccessarily have access to a standards-compliant libc (read Windows), or if you have need custom malloc/free implementations. Similar techniques are used in automake and similar build generators to provide non-standard libc function implementations. precompiled headers can help with the fact that good portions of the header will be #ifdef'ed away.
- asveikau 9y agoI think you completely missed the point of what you're replying to. You can put those reimplentations in a different file. You don't have to expose the consumer of the library to them by jamming the whole thing, both interface and implementation, in a single header. This is like library design by the folks who mistakenly say that what they do with '#include <stdio.h>' is they are "including a library". Eg. Compile and link are the same thing in a lot of people's minds, I think increasingly these days as C expertise is diminishing.
- pjmlp 9y agoYeah it looks like they are afraid of touching a linker.
- banachtarski 9y agoI develop on linux primarily but occasionally windows also. The stdlib is totally fine! When did you last compile on Windows? 2011?
- pjmlp 9y ago
- svdree 9y agoSean Barrett (who I think popularized the idea) has a FAQ on this (https://github.com/nothings/stb https://github.com/nothings/stb) where he justifies it by pointing at difficulties with deploying libraries on Windows. Which is a fair point, but by going straight to header-only he skips the step where you can also just distribute a bunch of headers and .C files. The convenience of only having to include a single header is nice for quick weekend projects, but for anything bigger you're dealing with dependencies and build issues anyway. I get some of the reasons that you would initially start out with a header-only implementation, but when your library grows, you probably want to split it at some point. For me personally, that point would be some time before the header reached 25k (!!) lines.
- chubot 9y agoThanks for the link. Why not two files, one a header and one an implementation? The difference between 10 files and 9 files is not a big deal, but the difference between 2 files and 1 file is a big deal. You don't need to zip or tar the files up, you don't have to remember to attach two files, etc. I'm still not convinced. I am convinced about a .c and .h -- that's how sqlite does it. Going to just a .h seems to provide negligible benefit and confuses the implementation and interface. It probably confuses a lot of tools too, e.g. source navigation tools, code coverage, code instrumentation, etc.
- flohofwoe 9y ago> and confuses the implementation and interface Normally you have the implementation inside an "#ifdef IMPLEMENTATION" block, and the API interface (public structs and functions) outside of the implementation block, and all private functions inside the implementation block are defined as 'static' so they are not visible outside the special implementation source file. In the places where you include the header for normal use, only the public interface is accessible.
- chubot 9y agoOK I get that the big .h file is still logically separated into an .h and .c, like you say. But what about preprocessing times? If you're including a library from many of your source files, then even if it always hits the #if 0 case, the preprocessor still has to parse the implementation. It matters for distributed compilation too -- more preprocessed bytes have to be sent over the network. I'm sure there are cases where this overhead is negligible. But I'm just as sure there are some where it's not. Not caring about how much text is in your headers seems like a bad habit to get into. Build times are the main reason I don't use C and C++ more.
- sam0x17 9y agoIt's really a godsend. include it and you're done.
- sam0x17 9y agoI've actually seen some projects where it's organized into a bunch of files, but then they export/"compile" to a single header file which is what you include in your project.
- wott 9y agoI have mostly seen it done by (young) people who come from web dev. They bring their bad habits together with them. And they usually don't get a very warm welcome for that. I mean, we've been nicely organising our sources in files, modules and libraries for 30 years, and that's been fine: very easy, logical and giving us many benefits (such as quick partial builds, splitting different operations, isolating problems for a much easier resolution, and so on) but suddenly they show up and it's too difficult for them and they prefer to dump thousands and thousands of lines in a single compilation unit and have all things interfere with each other...
- _dcwr 9y agoI have not heard this 'criticism' ever before and it's absolutely ridiculous on so many levels to me as a person tangentially interested in gamedev, C, C++, runtimes, system programming, retro stuff, history of gaemdev, etc. 1. Why would a web dev suddenly decide to write in such a spartan way an algorithm heavy library in C or C++? This makes no sense except to somehow tie modern web dev failures like left pad with this style of libraries to portray them in negative light... 2. In gamedev neat organization is/was not some golden long standing standard, i.e. in comments under[0] you can find information that with 3D graphics it was customary to use a single file to let the early compilers do a better job since they didn't optimize across translation units and many old codebases have no problem with thousands of lines of spaghetti per file. 3. These libraries are meant to be compiled once ever per project (except when you clean out all your object files) with a define in a single source file (which any modern compiler should handle), otherwise most of their text is thrown away in a single ifdef which should be trivial and speedy on a modern compiler. The resulting header shouldn't be a problem for any modern compiler and is as light or lighter than an entire include tree would be. Most of these libraries are also single purpose and have very small API and C is light compared to template heavy C++ anyway. If you develop such a library you can keep it split up and ship merged files, i.e. SQLite does that. If anything this might speed up the execution (optimization in single TU), compilation (a single TU for entire library) and linking (no dynamic nor static linking but a single object) but to me that tangential compared to convenience itself. 4. I've never seen such libraries to have been badly received - quite the contrary, SQLite might be the most widely deployed piece of software actually and it's recommended consumable form is a single pair of files (plus an ext file, for a total or 3 in the official 'amalgamation'). 5. I've personally yet to see a 'young web dev' create such a library. If anything it's the grizzled veterans of gamedev, runtimes, compression, middelware, cross platformness, systems programming, people with university education that includes CS heavy stuff, etc. Libraries I use or like that use this (single file or single header + source) style are: - very popular PD libraries by Sean T Barret, thanked in this linked repo's README. He arguably popularized this style, maintains a list of such libraries and has a FAQ and a guide about this style of libraries. He is over 50 (or maybe over 60, he was a teen in the 80s), is a gamedev veteran with software and hardware graphics and runtimes - Thief 1, Iggy (Flash runtime) at RAD, etc. His website is also hardly something that a modern web dev would make[1]. - pugixml by Arseny Kapoulkine - gamedev veteran: PS3, physics, rendering, etc. not a web dev, not young (definitely over 30, unless you happen to redefine young to 'under 40' which makes 0 sense in industry as young as ours but considering your other claims is totally likely). - SQLite which is shipped and recommended to be used like that (and I think Python embedds it like that) - D. Richard Hipp, Tcl, over 50 now, total veteran of world class between Fossil, Tcl, SQLite, etc., educated and programming since 90s, his personal website is also hardly modern web dev. - ClipperLib by Angus Johnson, a hardcore clipping 2D library, it has single/two file versions in C#, Delphi and C++ (are these the languages young web devs know well now too?) - another person who was a programme rin the 90s and is from very Enterprise-y background (Delphi Object Pascal, yikes), not a web dev, nor with a web dev worthy web page[3] (I don't mind it, I even find it nice to look at, but it's another hint of the lack of supposed web dev blood in him). Honorable mentions are the two people related to imgui concepts and also thanked in this linked repo's README: - Casey Muratori - programmer in the 90s already, over 30 or 40 years old, another (along STB) RAD tools related person, worked on Granny (animation for games) and Bink (video compression for games), total veteran in low level stuff, not a young web dev. - Omar Cornut - maker of Dear IMGUI, was an intern in the 90s, has a very non web dev website[4]. All in all I'm truly baffled about where you got the 'mostly young people who come from web dev' bit, especially since it's no secret many people are inspired to write them by STB himself (and mention that in their READMEs, as seen here) who is none of that. Your comment seems very hearsay, dishonest disingenuous. If anything I feel like this is a set up of a Yerevan (communist, post communist, Eastern Europe, Eastern Bloc, [5], etc.) joke I happen to know in Polish (translation mine): "Dear Radio Yerevan, is it true they give out free cars on the Red Square in Moscow?" - "That's mostly true but it's bikes, not cars, they are stolen, not given out, and not in the Red Square in Moscow but in the Prague district of Warsaw". [0] - http://fabiensanglard.net/duke3d/ http://fabiensanglard.net/duke3d/ [1] - http://nothings.org http://nothings.org [2] - http://www.hwaci.com/drh/ http://www.hwaci.com/drh/ [3] - http://www.angusj.com/delphi/clipper.php http://www.angusj.com/delphi/clipper.php [4] - http://www.miracleworld.net/ http://www.miracleworld.net/ [5] - https://en.wikipedia.org/wiki/Radio_Yerevan_jokes https://en.wikipedia.org/wiki/Radio_Yerevan_jokes
- intelhearts 9y agoIt's nice and simple, no dependencies no build systems or funky package manages I've been happy with using the various stb_ headers as well.. yeah not perfect or modern but honestly what's modern is a clusterfuck anyway
- CamperBob2 9y agoI think the real reason for this stance is that it imposes a sense of discipline that wouldn't otherwise be there. When you write a library like this one, you're always tempted to overengineer it... maybe it needs to render .PDFs (or render to them)? Wouldn't it be nice if it supported various OS clipboards? How about supporting both OpenGL and DirectWhatever and that homebrew software renderer that you wrote a few years ago? And wow, it sucks to build complex interfaces by aggregating C function calls. Lua support would only take a few more KLOCs, right? ... And before you know it, you're reinvented Qt. NTTAWWT, I guess, but if you set out to build a lightweight set of GUI controls for a specific purpose, you probably had a different goal in mind, and that's not the way to accomplish it. In my own work I frequently include stb libraries in separate .c(pp) files that provide a somewhat higher-level C or C++ interface to the features I need, which are then linked in the traditional way with .obj files and minimal headers. This approach works well, maintaining encapsulation and keeping compile times down while avoiding dragging in a ton of random dependencies.
- pjmlp 9y agoDevelopers that don't want to bother how to properly use a linker.
- wott 9y agoApparently, passing "-lmylib" to a compiler/linker has become a terrible effort indeed.
- JepZ 9y agoAs Ken Thompson [1] (the one from Ken Thompson and Dennis Ritchie (Unix & C)) was one of the original authors of Go, I think it is safe to use the Golang Origins FAQ [2] as a source to answer this question. > Dependency management is a big part of software development today but the “header files” of languages in the C tradition are antithetical to clean dependency analysis—and fast compilation. That might be a reason to limit the use of header files to single file. In addition, I think that it is meant to make the adoption of the library easier. [1]: https://en.wikipedia.org/wiki/Ken_Thompson https://en.wikipedia.org/wiki/Ken_Thompson [2]: https://golang.org/doc/faq#Origins https://golang.org/doc/faq#Origins