Macro Abuse in C

My friend rcfox on the Perl IRC network taught me how to abuse macros for fun and profit. Actually, he taught me how to abuse macros to better organize my code.

todo: expand with examples

Today I realized I could further abuse macros to organize things even more by taking advantage of variadic macros. rcfox happened to be online as I was discussing my success and pointed me to this brain-bending page. It talks about not just bending, but completely breaking the rules of C macro expansion to implement macro recursion. This came at a perfect time for me, as I was trying to do something like this:

#define PARAM_LIST \
/*                        1234567890123456    flags,        min,            max,    default */    \
    _PARAM(P_DI1TYPE,    "DI1 Type",        PF_ENUM,    EN_DITYPES,        0,    0)    \
    _PARAM(P_DI1FUNC,    "DI1 Function",    PF_ENUM,    EN_DIFUNCS,        0,    0)    \
    _PARAM(P_DI2TYPE,    "DI2 Type",        PF_ENUM,    EN_DITYPES,        0,    0)    \
    _PARAM(P_DI2FUNC,    "DI2 Function",    PF_ENUM,    EN_DIFUNCS,        0,    0)    \
    _PARAM(P_DI3TYPE,    "DI3 Type",        PF_ENUM,    EN_DITYPES,        0,    0)    \
    /* ... */

/* create an enum containing all the parameter IDs */
#define _PARAM(a,b,c,d,e,f)    a,
enum paramlist {
    PARAM_LIST
    NUM_PARAMS
};
#undef _PARAM

#define MENU_LIST \
/*          menu id         1234567890123456    parameter list for menu */    \
    _MENU(M_SYSTEM,        "System Menu",        P_DATETIME, P_DISPUNIT, P_DSTENABLE, P_DSTSTART, P_DSTEND, 0)        \
    _MENU(M_INPUTS,        "Inputs Menu",        P_DI1TYPE, P_DI1FUNC, P_DI2TYPE, P_DI2FUNC, P_DI3TYPE, P_DI3FUNC, 0)        \
    _MENU(M_RELAYS,        "Relays Menu",        P_DO1FUNC, P_DO2FUNC, P_DO3FUNC, P_DO4FUNC, P_DO5FUNC, P_DO6FUNC, 0)    \
    /* ... */

I was going to expand the _MENU macro into an array of structures like this in the C source file:

struct param {
    enum paramlist id;
    char *name;
    enum paramflags flags;
    int min;
    int max;
    int def;
    };

struct param param_list[NUM_PARAMS] = {
#define _PARAM(i,s,f,l,h,d) { .id=i, .name=s, .flags=f, .min=l, .max=h, .def=d },
    PARAM_LIST
#undef _PARAM
};

struct menu {
    const char * const name;        /* what to show on the display */
    unsigned int param_list[MAX_PARAMS_PER_MENU];
};

#define _MENU(a,b,...) { .name=b, .param_list={ __VA_ARGS__ } },
struct menu menus[NUM_MENUS] = { MENU_LIST };
#undef _MENU

This didn’t work though, because MENU_LIST expanded the parameter list to the literal text instead of the values that the P_WHATEVER list represented. I looked at the output of the preprocessor and saw this:

 struct menu menus[NUM_MENUS] = {
{ .name="System Menu", .param_list={ P_DATETIME, P_DISPUNIT, P_DSTENABLE, P_DSTSTART, P_DSTEND } },
{ .name="Inputs Menu", .param_list={ P_DI1TYPE, P_DI1FUNC, P_DI2TYPE, P_DI2FUNC, P_DI3TYPE, P_DI3FUNC } },
{ .name="Relays Menu", .param_list={ P_DO1FUNC, P_DO2FUNC, P_DO3FUNC, P_DO4FUNC, P_DO5FUNC, P_DO6FUNC } },
{ .name="Thermostat Menu", .param_list={ P_TSTATCAPS, P_TSTATMODES } },
{ .name="Security Menu", .param_list={ P_CARD1, P_CARD2, P_DOORTYPE1, P_DOORTIME1, P_DOORTYPE2, P_DOORTIME2, P_SIRENTYPE, P_SIRENTIME, P_SECKEY } },
{ .name="Jump to Param", .param_list={ } },
};

As you can see, it expanded _MENU correctly, but P_DATETIME (for example) did not expand.

The website contains the following explanation:

The easiest way of doing recursion in the preprocessor is to use a deferred expression. A deferred expression is an expression that requires more scans to fully expand:

#define EMPTY()
#define DEFER(id) id EMPTY()
#define EXPAND(...) __VA_ARGS__

#define A() 123
A() // Expands to 123
DEFER(A)() // Expands to A () because it requires one more scan to fully expand
EXPAND(DEFER(A)()) // Expands to 123, because the EXPAND macro forces another scan

Why is this important? Well when a macro is scanned and expanding, it creates a disabling context. This disabling context will cause a token, that refers to the currently expanding macro, to be painted blue. Thus, once its painted blue, the macro will no longer expand. This is why macros don’t expand recursively. However, a disabling context only exists during one scan, so by deferring an expansion we can prevent our macros from becoming painted blue. We will just need to apply more scans to the expression.

Wrapping my _MENU macro with an EXPAND macro as in the example provided on that site, I was able to get things to build:

#define EXPAND(...) __VA_ARGS__
#define _MENU(a,b,...) { .name=b, .param_list={ __VA_ARGS__ } },
struct menu menus[NUM_MENUS] = { EXPAND(MENU_LIST) };
#undef _MENU

The resultant expansion looks like this:

struct menu menus[NUM_MENUS] = {
{ .name="System Menu", .param_list={ P_DATETIME, P_DISPUNIT, P_DSTENABLE, P_DSTSTART, P_DSTEND } },
{ .name="Inputs Menu", .param_list={ P_DI1TYPE, P_DI1FUNC, P_DI2TYPE, P_DI2FUNC, P_DI3TYPE, P_DI3FUNC } },
{ .name="Relays Menu", .param_list={ P_DO1FUNC, P_DO2FUNC, P_DO3FUNC, P_DO4FUNC, P_DO5FUNC, P_DO6FUNC } },
{ .name="Thermostat Menu", .param_list={ P_TSTATCAP, P_TSTATMODE } },
{ .name="Security Menu", .param_list={ P_CARD1, P_CARD2, P_DOORTYPE1, P_DOORTIME1, P_DOORTYPE2, P_DOORTIME2, P_SIRENTYPE, P_SIRENTIME, P_SECKEY } },
{ .name="Jump to Param", .param_list={ } },
};

and the resultant assembler:

_menus:
        .quad   L_.str30
        .long   24
        .long   12
        .long   25
        .long   26
        .long   27
        .long   0
        .long   0

This construct allows _MENU to expand, and then the EXPAND macro causes all the P_WHATEVER lists to be evaluated again, neatly solving the problem.

Add picture from clipboard (Maximum size: 1 GB)