Design Con 2015

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

Andrew Lucas

January 01, 2013

Andrew LucasJanuary 01, 2013

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.

< Previous
Page 1 of 2
Next >

Loading comments...

Most Commented

  • Currently no items

Parts Search Datasheets.com

KNOWLEDGE CENTER