Code generation with X-Macros in C

August 2009

Original article on blog.brush.co.nz

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.

Comments

Greg Johnson 25 Aug 2009, 12:22

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;

Ben 25 Aug 2009, 12:49

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++.

Cameron Kerr 26 Aug 2009, 11:52

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:

  1. use a Makefile to generate eg. foo.c from foo.c.m4
  2. use --prefix-builtins with m4
  3. 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.

John Hyde : Site Doublers 4 Sep 2009, 17:07

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)

Ben 4 Sep 2009, 20:48

Heh, thanks John for that DRY humour. I hope my article didn’t fall into the same trap trap trap trap trap. :-)

Coder 23 Nov 2010, 06:37

Thanks for posting the this source code, very helpful!

SEO Jim 14 May 2011, 01:53

enum_print(enum foo_t foo) >>> this was major! Thank you all!

Yu 26 Mar 2013, 14:35

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?

Ben 26 Mar 2013, 14:48

@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.

Yu 27 Mar 2013, 12:59

@Ben Thank you.

Philip Ashmore 19 Oct 2017, 00:13

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!

Marcel 24 Jan 2018, 23:01

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 ;-)