Programming for a better tomorrow: the X-Macro

Here’s a C language technique which I’d not seen before starting my current job, which I think deserves to be better known.

Let’s say you have a bunch of things, to which you’ve given sequential integer IDs using an enum.

  enum my_things {
  thing_this,
  thing_that,
  thing_other,
  num_things
  };


For each of those things, you want to store some other information in various arrays, and index into the array using the enumerated value to pull that information out. Examples might be a parser (where the enums might identify operators, and an array might contain pointers to functions to implement them) or a message handling system (where the enums might identify types of message, and the array might contain pointers to functions to handle the messages).

void (*thing_fns[num_things])(void) = {
  do_this,
  do_that,
  do_other
  };
  
  void do_thing(enum my_things current_thing)
  {
  thing_fns[current_thing]();
  }
  
  void do_this(void)
  {
  /* wibble */
  }
  /* etc, etc. */
  


The problem comes when you want to add a new thing. You’re sizing the array using the enum, so it’s hard to get that wrong. But if you have a lot of things, sooner or later someone will add a new my_things member to the middle of the list (perhaps you’re listing your things in some order which makes sense in the context), go to update thing_fns, miscount how far down the definition thing_fns they’ve gone and screw up the ordering of the function pointers. Now the wrong function handles some (but not all) of the things, with hilarious results.

The solution is to maintain a single list of your things, and transform it into definitions for the enum and the associated arrays. You don’t need to write fancy code generator scripts for this, you can use the pre-processor. Wikipedia explains how.

(Note that Wikipedia’s example does away with sizing the array using the enum. They need to have a final NULL entry with no comma on the end to keep the compiler happy, so they’d end up writing the clunky num_things + 1. Now their array is sized correctly anyway.)

This chap goes into it in more detail, and also illustrates that your list of things doesn’t have to be in a seperate file.

Re-writing my example to use X-macros is left as an exercise for the reader. 🙂

Edited To Add: gjm11 points out that two of his friends have posted about this recently. gareth_rees has a more detailed example and some discussion.

8 Comments on "Programming for a better tomorrow: the X-Macro"


  1. They need to have a final NULL entry with no comma on the end to keep the compiler happy, so they’d end up writing the clunky num_things + 1

    Initializers have always allowed a trailing comma, it’s only enum-specifiers that used to prohibit it. (However, the AIX native compiler does enforce the prohibition, so you can’t just put it in anyway in portable code.)

    Reply

    1. I’m used to compilers which will at least warn about the trailing comma (presumably on the grounds that you might have missed something). One of the ones I’ve worked with did consider it an error, ISTR.

      Reply

      1. Ouch. OK, it’s a neat trick, but it’s exactly the sort of confusing macro code we always try to avoid at work. I’d rather take the enum and separate array definition: we do this sort of thing often, we have ample experience to decide which approach is less error-prone, and it is not a difficult conclusion to draw.

        In any case, isn’t this an idea whose time came and went about twenty years ago? Just about all modern programming languages support dictionary types or similar language features that deal with this problem trivially. It always seems sad when we have to write hacks like this because we’re stuck using C. 🙁

        Reply

        1. Horses for courses: if you’re programming small devices, then C is still where it’s at. For many platforms, there aren’t the compilers, debuggers, and libraries for anything other than C (or C++).

          I have ample experience too and I find the X-macro approach works well. So maybe there are good and bad ways to use it.

          Reply

Leave a Reply

Your email address will not be published. Required fields are marked *