Jacques Mattheij

Technology, Coding and Business

C Preprocessor Hell

Lisp programmers should stop reading right now because they’ll likely suffer severe injury of the jaw muscles as they laugh themselves silly at how hard it is to do some things in C.

The C language has a pre-processor (typically called cpp) that is both infuriating and powerful.

How powerful is usually best described as ‘just too little’ and it has happened more than once that I found myself almost - but not quite - able to do what I wanted to do.

The frustration can run very deep at times like these. Recently I had another battle with the C pre-processor to write a set of macros to accomplish the goal of hiding a bunch of complexity from the day-to-day view of the programmers that will use the system I was working on.

The core is a macro that constructs a function, functions can have a variable number of arguments and each of these arguments have to be copied to the appropriate variables in some structure.

At first glance an easy job for a variadic macro, a fancy way of saying ‘a macro that will accept a variable number of arguments’. Unfortunately the C pre-processor will make this ‘easy job’ into one that after some study appears to be impossible.

After knocking my head against the problem for a while I remembered seeing a nifty macro that can return the number of arguments passed to it. (see: http://stackoverflow.com/questions/2308243/macro-returning-the-number-of-arguments-it-is-given-in-c ). This macro is really neat and a good part of the solution except for the problem that it will not work when you pass it zero arguments.

The reason why you can’t pass it 0 arguments has to do with the C99 standard, as stated in the note on that page “using PP_NARG() without arguments would violate 6.10.3p4 of ISO C99”, in short, that inner macro needs at least one argument passed to it…

Of course, my use case involves being able to call the macro with no arguments at all. So close…

After fiddling around with it for a while I came up with the following solution:

#define REVERSE 5, 4, 3, 2, 1, 0 #define ARGN(1, _2, _3, _4, _5, N, …) N #define NARG(dummy, …) ARGN(VA_ARGS) #define NARG(…) NARG_(dummy, ##VA_ARGS, REVERSE)

The dummy argument and the ## in front of the VA_ARGS gets rid of the limitation and now the macro happily reports ‘0’ when passed no arguments. After the first level of expansion the dummy argument is dropped and everything is much like it was in the stackoverflow example. You can easily increase the 5 parameter limit if you need more arguments, just make the REVERSE and ARGN lists longer.

To verify this really works:

NARG() NARG(a) NARG(a,b)

transforms into

0 1 2

You can use this to plug it into a cascade of macros to pick out the right parts to concatenate like this:

#define SPLICE(a,b) SPLICE_1(a,b)
#define SPLICE_1(a,b) SPLICE_2(a,b)
#define SPLICE_2(a,b) a##b

#define FIELD_0(...)

#define FIELD_1(field, ...) \

#define FIELD_2(field, ...) \
  somestruct.field=field; FIELD_1(__VA_ARGS__)

#define FIELD_3(field, ...) \ 
  somestruct.field=field; FIELD_2(__VA_ARGS__)

#define FIELD_4(field, ...) \
  somestruct.field=field; FIELD_3(__VA_ARGS__)

#define FIELD_5(field, ...) \
  somestruct.field=field; FIELD_4(__VA_ARGS__)

#define FIELDS_(N, ...) \

#define FIELDS(...) \

FIELDS(a, b, c)

And presto, problem solved!

The multiple wrapping of the ## operator ensures expansion of nested macros, the ## operator itself concatenates two parts, and in the ##__VA_ARGS part of the FIELDS macro it helps to drop the preceding comma in case there are no arguments (otherwise it would read as “(0, )” after expanding.

The sample above will expand to somestruct.a=a; somestruct.b=b; somestruct->c=c;

Just ‘FIELDS()’ without arguments will expand to a blank string.

Of course, it’s not exactly Rocket Science but there you have it :)

Many thanks to Laurent Deniau who wrote the original version of the heart of this trick, that really saved the day, and I hope this improved version will one day help someone else out in a similar way.

HN Submission/Discussion
If you read this far you should probably follow me on twitter: