Code generation with X-Macros in C
C and C++ are relatively non-dynamic languages, and one thing this means is that not repeating yourself (aka DRY) is often harder than in a language like Python.
For instance, when you’ve got a config file, a config structure, config defaults, and a config printer, you want all those things to come from a single spec. One good way around this problem is code generation — for example, using an XML spec with Python and Cheetah templates to generate C code.
But for simple C projects this can be overkill. And it turns out the age-old C preprocessor contains a few goodies that help with DRY programming. As the Wikipedia article says, one little-known usage pattern of the C preprocessor is known as “X-Macros”.
So what are X-Macros?
An X-Macro is a standard preprocessor macro (or just a header file) that contains a list of calls to a sub-macro. For example, here’s the config.def
file for the INI-parsing code we’ll be looking at (uses my simple INI parser library):
/* CFG(section, name, default) */
CFG(protocol, version, "0")
CFG(user, name, "Fatty Lumpkin")
CFG(user, email, "fatty@lumpkin.com")
#undef CFG
That’s an X-Macro that defines a config file with a protocol version and user name and email fields. If we weren’t following DRY, our main code would specify the field names in the struct
definition, repeat them for setting the default values, and repeat them again for loading and printing the structure.
To do this in X-Macro style, we just #include "config.def"
repeatedly, but #define CFG
to what we need each time we include it. Sticking with show-me-the-code, here’s a program that loads, stores, and prints our config:
#include <stdio.h>
#include <string.h>
#include "../ini.h"
/* define the config struct type */
typedef struct {
#define CFG(s, n, default) char *s##_##n;
#include "config.def"
} config;
/* create one and fill in its default values */
config Config = {
#define CFG(s, n, default) default,
#include "config.def"
};
/* process a line of the INI file, storing valid values into config struct */
int handler(void *user, const char *section, const char *name,
const char *value)
{
config *cfg = (config *)user;
if (0) ;
#define CFG(s, n, default) else if (stricmp(section, #s)==0 && \
stricmp(name, #n)==0) cfg->s##_##n = strdup(value);
#include "config.def"
return 1;
}
/* print all the variables in the config, one per line */
void dump_config(config *cfg)
{
#define CFG(s, n, default) printf("%s_%s = %s\n", #s, #n, cfg->s##_##n);
#include "config.def"
}
int main(int argc, char* argv[])
{
if (ini_parse("test.ini", handler, &Config) < 0)
printf("Can't load 'test.ini', using defaults\n");
dump_config(&Config);
return 0;
}
Note that config.def
is included 4 times, so you’d have to repeat yourself 3 times with no X-Macros. I admit it’s not beautiful artwork. But it’s not too ugly either — and it gets the job done with nothing but C’s built-in code generator.
21 August 2009 by Ben 14 comments
14 comments and pings (oldest first)
This is a nice techique for creating printable string representations of enums. Define the enums as X-MACROS,
include the file where the enum is to be defined with the
macro expanding to adding a trailing comma, and then include
the file in an enum_print(enum foo_t foo)
inside a switch statement with the X-MACRO expanding to something like
case n: printf("%s", n); break;
As gauchopuro mentioned on prog.reddit, there’s a related technique known as supermacros. See Stephan Beal’s paper, Supermacros: Powerful, maintainable preprocessor macros in C++.
I have done this in the past when I wanted to create a library useful for storing uint32_t
or uint64_t
types, and had initially used CPP in this way. However, you may very well find that M4 can be more useful/powerful for this, and avoids many of CPP’s limitations (although it adds a few of its own).
Three tips for using M4:
- use a Makefile to generate eg. foo.c from foo.c.m4
- use
--prefix-builtins
with m4 - using
--synclines
will help to more quickly jump to errors because your error messages etc will refer to the m4 file, not the generated C file.
Hi I can only add some humour to this discussion.
It was a preso about Ruby-on-Rails. The speaker said “don’t repeat yourself” about 6 times in the first couple of minutes.
I couldn’t take any more so I started a tally chart. The girl next to me started giggling when she spotted this. Then someone else started to cough each time he said it.
The moral of the story: how to get the message out there about DRY – without too much RY.
(Or don’t sit too near to me at a preso)
Heh, thanks John for that DRY humour. I hope my article didn’t fall into the same trap trap trap trap trap. :-)
Thanks for posting the this source code, very helpful!
enum_print(enum foo_t foo) >>> this was major! Thank you all!
It seems so nice. so ….. it is only a “parser”,and i can not write sth. down… Any suggestion can I write something into the ini file?
@Yu, I’m afraid “inih” is only a parser, and can’t write out .INI files. If you need to do that within your app, you’ll either need to write it manually (using stdio functions), or use another library.
@Ben Thank you.
I git cloned https://github.com/MrBuddyCasino/ESP32MP3Decoder.git and was missing a submodulle https://github.com/benhoyt/inih.git After some reading I discovered X-Macros. The thing is, I’ve been using this technique in my projects for years, I called them templates. Check out my SourceForge projects: treedb, meta-treedb, v3c-dcom. In these projects I went to town and painted it red with X-Macros/templates!
[…] with no additional tools and languages. I’m not the only developer using this scheme as this blogs X-Macros show. This wouldn’t be possible to do without the missing automatic […]
I use the very same technique since decades to create all sorts of stuff out of the DRY department in the Embedded Systems Field e.g.: Stub generators for communication protocols or whole communication protocols, configuration, non volatile memory, shell interpreters, testing frameworks and so on. One of the many advantages is the perfect portability of ANSI-C plus Macro-Magic based projects. No need for external tools which might not be available nor run, no hassles when the code is ported to another platform, architecture, compiler + tools chain, IDE, make system or any. It just works. And one gains so much speed in enhancing or changing the parts based on these macro-technique. So one can be ways more productive compared to standard RY techniques. The disadvantage is: nobody who is not familiar the this type of coding will understand it easily. So you will end up beeng responsible for your code till you die. But for some this might be an advantage as well ;-)
[…] Code generation with X-Macros in C :: The Brush Blog (tags: c macros) […]