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

", #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

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

Add a comment