You have less worries
Yes, the sky is blue

Code generation with X-Macros in C

21 August 2009, by Ben    11 comments

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.

11 comments and pings (oldest first)

Ping: Blarney Fellow 22 Aug 2009, 13:12 link

[...] Code generation with X-Macros in C :: The Brush Blog (tags: c macros) [...]

Greg Johnson 25 Aug 2009, 12:22 link

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 link

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 link

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 link

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 link

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 link

Thanks for posting the this source code, very helpful!

SEO Jim 14 May 2011, 01:53 link

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

Yu 26 Mar 2013, 14:35 link

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 link

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

@Ben Thank you.

Add a comment

We reserve the right to edit or remove your comment if it’s spammy, offensive, unintelligible, or if you don’t give a valid email address. :-)

We’ll never share your email address.