The Case For config.h
You’ve written a program. Awesome! You’re not a software tyrannt, though, so you want the user to be able to tweak some things here and there… and possibly save their preferences. Great, let’s store their settings into… wait… How do we do that? A config file? gsettings? The Windows Registry? OK, that’s a bit of a stretch… or do we ask the user to set things up at compile-time? A config.h file, perhaps?
Configuration source modules have very bad fame, mainly due to dwm’s bad fame of being hard to use… mainly because of this. Actually, I believe dwm is the best example of a project that abuses config.h, but more on that later… Oh! gasps Ariadna doesn’t believe config.h to be the definite solution to deal with user configuration! Ariadna is not a total extremist!!
Nope, I’m not. Sorry to disappoint you.
In software there are two main ways for users to configure things: methods that work on runtime and methods that work on compile-time. That’s all. Most people resort to the former, while config.h or modifying variables in a Makefile are compile-time methods. What’s best? It depends, it always depends…
Pick your poison!
If you wanna go the config file route… Do you really want to implement
subroutines which will solely exist for accessing and manipulating your
configuration file, as well as some means to create those dreaded
$XDG_CONFIG_HOME/myapp, etc? What about
having supporting system-wide configuration files under
/etc? All of that
is very standard, but puts quite a burden over your application just to know
where to look for the files.
Then you have to actually parse your configuration file. Do you roll your own format and parser? Do you shop for one of the popular declarative formats like .conf (libconfig), TOML, YAML… XML (ohnoes)…? They do most of the heavy lifting, for sure… but it means you add dependencies into your program! By the way, are you going to provide for a config UI in your program or are you going to ask users to edit the files themselves?
On the other hand, config.h files work during compile-time: set up some variables, use those values to modify parameters in the source code… And done.
By the way! In interpreted languages, you can do exactly the same. If you’re writing a program in, let’s say, Python… You can always have a module that sets the variables you want the user to be able to modify and import it! This would not be a header technically… but the effect is totally the same. ranger uses this idea, for example. So, this isn’t just a thing of us C nutheads, ok?
OK, OK… That’s all from the POV of the developers. What about the POV of your users?
For users the main difference is that run time-based approaches allow for editing their preferences more quickly and that they allow for having multiple configurations: one for each user and maybe even a system-wide configuration file. In a config header approach, well, users have to set their preferences as part of the binary. If you install the binary into any of the system-wide locations, that same configuration will be forced upon all users… It also can become cumbersome to edit preferences and then compiling the program just because you were testing a new setup. No fun, no.
Oh, and if you happen to grab the program from a binary package, you’re screwed… It’s the package mantainer who forces their decisions onto you!1
To me, config headers shine as the best solution for small utilities that are configured once and that’s it. And that’s what I tend to write, so that’s why all of my current projects use that! It economizes so much in such a case… and compilation time in this kind of projects is absolutely negligible.
Of course there are some trade-offs.2 For instance, if you go the static
variables route (instead of using
#define macros) because you prefer to
be able to check the types of variables, you know the compiler might warn you
of unused variables if you include
config.h into multiple modules. Config
headers work best if you use them to set things in the main module solely.
Also, you may get yourself into injecting quite a good amount of global
variables… which might get a bit messy.
So, my point. I think they work wonders for small projects and that’s why I use them as the default option. If your projects grow to become more complex or they’re meant to be some sort of persistent program, I would totally go the config file route. The latter is exactly why I think dwm is a bad example: a WM is something you wanna test different settings for before staying put with a configuration that you like. Recompiling it every single time is… a bit too extreme. A terminal emulator, huh?… that’s way more tolerable to my taste: it’s not literally the foundation of your workspace! Imagine recompiling a web server every single time you need to change something on it…3 it’d be totally impractical!
Everyone knows better about their own projects…
and also about anyone else’s But, I would say that if you’re writing small
tools, compile time-based configuration is something you should consider! Why
not? I do believe it can save you from writing quite a lot boilerplate code
that isn’t directly related to your goals.
If it doesn’t work for you, though, just use a config file and call it a day. No big deal. No drama.
So, if you really wanna enjoy minimalist software… avoid packaged versions. ↩︎
Do we need to say that? It’s always like that, isn’t it? ↩︎
I fortunately don’t know of any web server that uses config headers… and I ask you very kindly you don’t send me any examples, because probably I’d hurt myself facepalming if I saw any! ↩︎