Standard C-macro scripting

Peter Jeremy PeterJeremy at
Thu Dec 15 01:31:11 PST 2005

On Wed, 2005-Dec-14 01:05:45 +0100, Hans Petter Selasky wrote:
>On Tuesday 13 December 2005 20:48, John Baldwin wrote:
>> Honestly, I think I've now been scarred for life. :-/  I think that this
>> stuff would be so obscure that no one else would be able to help with
>> maintenace.

I tend to agree.

>Macros are easy. It is just concat, stringify and expand. Maybe you have to 
>think more about it before you get it. I am probably too used to it.

When you have nested macros and have to work out what is stringified at
each level of expansion, it can get very difficult to follow.

>What is the alternative? An Awk script would require a lot more code and it 
>cannot be called from a C-program when it is compiling.

The approach I use (borrowed from gcc) is multiple includes.  To take
your enum example:

========== foo.def ===================
m(MY_ENUM_UNKNOWN  ,,"unknown")
m(MY_ENUM_YYY      ,,"yyy ...")
m(MY_ENUM_DAIC     ,,"zzz ...")
========== foo.c =====================
enum MY_ENUM {
#define m(a,b,c)	a b,
#include "foo.def"
#undef m

static const char * const MY_ENUMS_DEFAULT_DRIVER_DESC[] = {
#define m(a,b,c)	c,
#include "foo.def"
#undef m
========== EOF ======================

Personally, I think this is far easier to understand - a single macro
which is redefined to have a number of simple macro expansions rather
than seven macros with carefully organised nesting to ensure correct
stringification.  I agree that this requires re-reading the definition
file but it will be cached after the first pass so this isn't particularly
expensive.  You also have the advantage that the data (foo.def) doesn't
have to be a single statement so you can (more easily) embed comments or
other preprocessor directives.

gcc uses this for various *.def files, redefining macros to suit requirements.

As a real (cut-down) example from an interpreter I'm maintaining (in
reality optab.h has 146 entries with 5 different opX_XX() macros):

=============== optab.h ==================
opd_dd(ex_add,    ADD,    "add",)         /* 2   A+B    */
opd_d( ex_plus,   PLUS,   "plus",)        /* 3    +B    */
opd_dd(ex_sub,    SUB,    "sub",)         /* 4   A-B    */
opd_d( ex_minus,  MINUS,  "minus",)       /* 5    -B    */
opd_dd(ex_pwr,    PWR,    "pwr",)         /* 16  A*B    */
opd_d( ex_exp,    EXP,    "exp",)         /* 17   *B    */
opd_dd(ex_comb,   COMB,   "comb",)        /* 22  A!B    */
opd_d( ex_fac,    FAC,    "fac",)         /* 23   !B    */

#undef opd_d
#undef opd_dd
===========  opt_codes.h ============
enum OpCode {
#define opd_d(a,b,c,d)  b,
#define opd_dd(a,b,c,d) b,
#include "optab.h"
    OPT_MAX     /* largest opt-code */
=============== optable.c ==============
const pfd_d exop2[] = {
#define opd_d(a,b,c,d)  a,
#define opd_dd(a,b,c,d) ex_botch2,
#include "optab.h"

const pfd_dd exop3[] = {
#define opd_d(a,b,c,d)  ex_botch3,
#define opd_dd(a,b,c,d) a,
#include "optab.h"

const char *opname[] = {
#define opd_d(a,b,c,d)  c,
#define opd_dd(a,b,c,d) c,
#include "optab.h"

=============== main.h ==============
#define opd_d(a,b,c,d)  data a(data) d;
#define opd_dd(a,b,c,d) data a(data, data) d;
#include "optab.h"

========== EOF ======================

>Here is a real example of a state machine:
>#define L3_STATES(m)/* \
>m( ST_L3_UC_TO    ,, 4/*hz*/, ST_L3_U0    , "Disconnected"          , 0x0C  )\
>Isn't the state-machine above easy to edit and understand ?

It looks the same as my definitions file (modulo a backslash on each line
and not having it in a separate file).

>What is wrong about that?

The complexity of the macros you need to expand it.  There's also no
scope for conditional inclusion (what if you wanted a single state
machine description to conditionally compile for several protocol

>And how would you solve it?

See above.

Peter Jeremy

More information about the freebsd-hackers mailing list