Reduce C-language coding errors with X macros - Part 1

Editor's note: Andrew Lucas describes how to use X macros to take advantage of the C-language pre-processor to eliminate several classes of common bugs. He also describes how to use X macros to improve developer productivity through automatic code generation.

X macros are a powerful coding technique that makes extensive use of the C-language pre-processor. This technique has the capability to eliminate several classes of common bugs.

It seems to me that the C preprocessor gets a bad rap. Granted, there are ways to use the preprocessor inappropriately, but to limit its use because of that constrains a valuable tool that can reduce coding errors and improve developer productivity though automatic code generation.

Code Ordering DependenciesI discovered X macros a few years ago when I started making use of function pointers in my code. Frequently I would write code like this:

The issue with this type of code is maintainability. The ordering of the array initializers has to match the ordering of the state code enumeration exactly. Historically I would comment this type of code liberally to warn future users about this dependency, but protection based on commenting is really no protection at all. What I needed was a tool that would automatically enforce the dependency.

I began investigating solutions for this problem and discovered that in the C99 standard there was a new way to initialize arrays. An improved way to write the above code is as follows:

Now even if I change the ordering of the enumeration, the jumptable logic doesn’t break. Much better. My only problem was that the C compiler I was working with was not compliant with the C99 standard. Back to square one.

X macros to the RescueOne day while talking shop with a friend of mine, I explained my problem and he suggested using the C preprocessor to enforce the ordering. He explained the basic concept: Use preprocessor directives to define a table in the form of a macro and then redefine how the macro is expanded, as required.Here's how this technique enforces my code ordering dependency:

In the case of the enumeration the table expands to ‘a’ which is the first column of the state table; the state code. In the case of the array, the table expands to ‘b’ which is the second column, the name of the function pointer.

The code based on the X macro table is expanded in the same order for both the enumeration and the array. The preprocessor now enforces the dependency!

Cleaning up the codeOne thing I don’t like about this implementation is the presence of #define and #undef throughout the code, which to me is ugly and makes the code less readable. Let’s look at a technique for getting rid of them.

You will notice that in my definition of the STATE_TABLE macro I don’t take any parameters. There is nothing to prevent me from passing the definition of ENTRY directly to the STATE_TABLE macro instead of defining it separately:

Much better, but is there anything else that we could use the X macro table for? Since every function pointer corresponds to an actual function, we could use the table to generate function prototypes for us:

Register InitializationThat's not the only way X macros can be used. In my code I commonly have to interface to custom FPGAs. These devices usually have many memory mapped registers that need initialization. It's easy to forget to initialize a newly defined register, but using X macros, this is another task we can automate.

Simple; and as new registers are added, no code needs to be updated to initialize it - we just add a row to the table and the preprocessor does the rest. We can further improve this code to take into account not only the initialization, but the declaration of the registers:

This code uses a compiler specific directive _at_ to place the variables at absolute addresses. This may not be possible with other compilers. Secondly, more than one table may be required to take into account different types of register declarations. You may need to have a read-only register table, a write-only register table, an uninitialized register table, etc.

I hope that this introduction to X macros has provided a glimpse into the power of this coding technique. In Part 2 I dig a little deeper and show some more advanced uses of X macros to facilitate automatic code generation.