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 Dependencies
I 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:

/* declare an enumeration of state codes */
enum{STATE_0, STATE_1, STATE_2, … , STATE_N, NUM_STATES};

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {func_0, func_1, func_2, … , func_N}
;

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:

/* declare an enumeration of state codes */
enum{STATE_0, STATE_1, STATE_2, … , STATE_N, NUM_STATES}

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {
         [STATE_1] = func_1,
         [STATE_0] = func_0,
         [STATE_2] = func_2,
          … ,
         [STATE_N] = func_N
};

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 Rescue
One 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:

#define STATE_TABLE            
        ENTRY(STATE_0, func_0)
        ENTRY(STATE_1, func_1)
        ENTRY(STATE_2, func_2)
        …                   
        ENTRY(STATE_X, func_X)

/* declare an enumeration of state codes */
enum{
#define ENTRY(a,b) a,
    STATE_TABLE
#undef ENTRY
    NUM_STATES
};

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {
#define ENTRY(a,b) b,
    STATE_TABLE
#undef ENTRY
};

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 code
One 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:

#define EXPAND_AS_ENUMERATION(a,b) a,
#define EXPAND_AS_JUMPTABLE(a,b) b,
#define STATE_TABLE(ENTRY)     
        ENTRY(STATE_0, func_0)
        ENTRY(STATE_1, func_1)
        ENTRY(STATE_2, func_2)
        …                   
        ENTRY(STATE_X, func_X)

/* declare an enumeration of state codes */
enum{
    STATE_TABLE(EXPAND_AS_ENUMERATION)
    NUM_STATES
}

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {
    STATE_TABLE(EXPAND_AS_JUMPTABLE)
}:

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:

#define EXPAND_AS_PROTOTYPES(a,b) static void b(void);
STATE_TABLE(EXPAND_AS_PROTOTYPES)
;

Now I no longer need to remember to add a prototype when I add new states. The preprocessor can take care of it and will expand the table into the following code automatically:

static void func_0(void);
static void func_1(void);
static void func_2(void);

static void func_X(void);

Register Initialization
That'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.

#define EXPAND_AS_INITIALIZER(a,b) a = b;
#define REGISTER_TABLE(ENTRY)
    ENTRY(reg_0, 0x11)       
    ENTRY(reg_1, 0x55)       
    ENTRY(reg_2, 0x1b)       
    …                       
    ENTRY(reg_X, 0x33)
static void init_registers(void){
         REGISTER_TABLE(EXPAND_AS_INITIALIZER)
}

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:

#define FPGA_ADDRESS_OFFSET (0x8000)
#define EXPAND_AS_INITIALIZER(a,b,c) a = c;
#define EXPAND_AS_DECLARATION(a,b,c) volatile uint8_t a _at_ b;
#define REGISTER_TABLE(ENTRY)                   
    ENTRY(reg_0, FPGA_ADDRESS_OFFSET + 0, 0x11)
    ENTRY(reg_1, FPGA_ADDRESS_OFFSET + 1, 0x55)
    ENTRY(reg_2, FPGA_ADDRESS_OFFSET + 2, 0x1b)
    …                                         
    ENTRY(reg_X, FPGA_ADDRESS_OFFSET + X, 0x33)

/* declare the registers */
REGISTER_TABLE(EXPAND_AS_DECLARATION)

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.

Part 2

Andrew Lucas leads a team of firmware developers at NCR Canada. He is responsible for the firmware architecture of their intelligent deposit modules found inside NCR's line of ATMs.

X macros are a powerful coding technique that makes extensive use of the C-language pre-processor (http://en.wikipedia.org/wiki/C_preprocessor). 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 Dependencies
I 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:

/* declare an enumeration of state codes */
enum{STATE_0, STATE_1, STATE_2, … , STATE_N, NUM_STATES};

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {func_0, func_1, func_2, … , func_N};

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:

/* declare an enumeration of state codes */
enum{STATE_0, STATE_1, STATE_2, … , STATE_N, NUM_STATES};

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {
         [STATE_1] = func_1,
         [STATE_0] = func_0,
         [STATE_2] = func_2,
          … ,
         [STATE_N] = func_N
};

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 Rescue
One 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:

#define STATE_TABLE            
        ENTRY(STATE_0, func_0)
        ENTRY(STATE_1, func_1)
        ENTRY(STATE_2, func_2)
        …                   
        ENTRY(STATE_X, func_X)

/* declare an enumeration of state codes */
enum{
#define ENTRY(a,b) a,
    STATE_TABLE
#undef ENTRY
    NUM_STATES
};

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {
#define ENTRY(a,b) b,
    STATE_TABLE
#undef ENTRY
};

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 code
One 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:

#define EXPAND_AS_ENUMERATION(a,b) a,
#define EXPAND_AS_JUMPTABLE(a,b) b,
#define STATE_TABLE(ENTRY)     
        ENTRY(STATE_0, func_0)
        ENTRY(STATE_1, func_1)
        ENTRY(STATE_2, func_2)
        …                   
        ENTRY(STATE_X, func_X)

/* declare an enumeration of state codes */
enum{
    STATE_TABLE(EXPAND_AS_ENUMERATION);
    NUM_STATES
};

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {

    STATE_TABLE(EXPAND_AS_JUMPTABLE);
};

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:

#define EXPAND_AS_PROTOTYPES(a,b) static void b(void);
STATE_TABLE(EXPAND_AS_PROTOTYPES);

Now I no longer need to remember to add a prototype when I add new states. The preprocessor can take care of it and will expand the table into the following code automatically:

static void func_0(void);
static void func_1(void);
static void func_2(void);

static void func_X(void);

Register Initialization
That'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.

#define EXPAND_AS_INITIALIZER(a,b) a = b;
#define REGISTER_TABLE(ENTRY)
    ENTRY(reg_0, 0x11)       
    ENTRY(reg_1, 0x55)       
    ENTRY(reg_2, 0x1b)       
    …                       
    ENTRY(reg_X, 0x33)

static void init_registers(void){
         REGISTER_TABLE(EXPAND_AS_INITIALIZER);
};

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:

#define FPGA_ADDRESS_OFFSET (0x8000)
#define EXPAND_AS_INITIALIZER(a,b,c) a = c;
#define EXPAND_AS_DECLARATION(a,b,c) volatile uint8_t a _at_ b;
#define REGISTER_TABLE(ENTRY)                   
    ENTRY(reg_0, FPGA_ADDRESS_OFFSET + 0, 0x11)
    ENTRY(reg_1, FPGA_ADDRESS_OFFSET + 1, 0x55)
    ENTRY(reg_2, FPGA_ADDRESS_OFFSET + 2, 0x1b)
    …                                         
    ENTRY(reg_X, FPGA_ADDRESS_OFFSET + X, 0x33)

/* declare the registers */
REGISTER_TABLE(EXPAND_AS_DECLARATION);

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 a future article I plan to dig a little deeper and show some more advanced uses of X macros to facilitate automatic code generation.

Read Part 2
Read Part 3

Andrew Lucas leads a team of firmware developers at NCR Canada. He is responsible for the firmware architecture of their intelligent deposit modules found inside NCR's line of ATMs.

12 thoughts on “Reduce C-language coding errors with X macros – Part 1

  1. Thanks for the article – love it. This will come in handy for sure.

    You need to remove the semicolon when expanding the table within enum and struct definitions in actual use. The code 'STATE_TABLE(EXPAND_AS_ENUMERATION);' would become 'STATE_TABLE(EXPA

    Log in to Reply
  2. I do a similar technique but use a xxx.tbl and include it as needed. I include it first in a xxxx.h file to create a common set of enum values, then several times in a xxx.c file to create tables the code can search.

    This also allows me to define several

    Log in to Reply
  3. C is certainly the language we all love to hate, but C has one killer feature: the macro pre-processor. Until Ada and other pretenders for the crown grow a proper pre-processor language, they will never get very far.

    The pre-processor is especially useful

    Log in to Reply
  4. OTOH, the C pre-processor is pathetically limited when compared to *real* macros in most decent assemblers, going all the way back to IBM mainframe assembler in the 1960s. Because C only does text manipulation, without knowing the semantic context, *and*

    Log in to Reply
  5. Hi Lucas,

    Nice article, truly preprocessor is a very powerful C feature.

    I have one comment on the STATE_TABLE macro. I don`t think so this method also is as immune as the c-99 style initialization. There are scope for bugs in this case also. In this cu

    Log in to Reply
  6. You know what I really dislike about macros – they are impossible to debug.
    Any error that slips into your design under the guise of a macro will cause a lot of havoc before you are able to identify it.
    You can't see them in your debugger, your compiler

    Log in to Reply
  7. That is why in general I don't use macros to “inline” code. The macros explained in the article for the most part are used to initialize tables, something that can be very error prone to do by hand. These tables can then be easily verified for correctnes

    Log in to Reply
  8. I took the liberty of linking to this article from the Wikipedia.org article on “X Macro”. Hopefully this is okay. This is a wonderful article on a truly useful method, and deserves wider mindshare!

    Log in to Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.