Why the C preprocessor is a good thing
Submitted by Dan Muresan on
Yesterday, Christian Kienle argued that the C preprocessor is a bad thing. When a language lacks closures and garbage collection and forces static typing without type inference on its users, you would think that a moderately powerful feature like preprocessor macros would get some respect, at least in these times of programming-language renaissance when there are so many good alternatives.
First of all, I believe that Paul Graham's advice holds true in any language: macros should only be used when nothing else will do. But when that happens, avoiding macros leads to contorted or verbose solutions.
Let's look at Christian's arguments:
Debugging preprocessor macros is hard
It's true that most debuggers can't map compiled code back to the original macros. However, most debugging is (or should be) done outside debuggers, and debugging would be hard without the preprocessor:
- the preprocessor provides the
__FILE__
and__LINE__
macros. Yes, they could be predefined identifiers, just like C99's__func__
, but that's actually a less flexible solution: since C concatenates adjacent string literals, you can write"error in " __FILE__
, but you can't do that with __func__ assert
can only be written as a macro, since it needs to stringify the condition being tested- Without macros, you'd be forced to invoke logging primitives like in Java:
if(logger.isDebugEnabled() { logger.debug(expensive_function ()); }
- using macros and RAII in C++, you can write a tracing system
Preprocessor macros are not type-safe
True, and it's the closest thing that C/C++ have to type inference. Christian doesn't actually show how this supposed type-unsafety can bite you, but instead points to the next reason and suggests that you use templates in C++ (or NSNumbers in Objective C). I don't know about Objective C, but
template <class T1, class T2> bool min (T1 x, T2 y)
{ return x < y ? x : y; }
looks pretty verbose to me.
Preprocessor macros often lead to side effects
What this means is that macro arguments can appear multiple times in the macro-expansion:
#define MAX( a, b ) ((a) < (b) ? (a) : (b))
If one of the arguments is an expression with side effects (such as x++
or a function call that modifies some state), then we have a bug. This is true, but
- programming with side effects is not a good practice. Even if you don't have the luxury to program in a functional language, you should still strive to minimize reliance on side-effects
- macros are usually given capitalized names, like
MAX
, just so they scream at you when you are about to typeMAX (x++, f (y))
- if one of the arguments is a function
f()
, butf
has no side effects, the compiler may be able to optimize away redundant multiple invocations - you get what you pay for — this is not Lisp, after all.
Of the three arguments against macros, only the last one is actually a serious objection; and just because the C preprocessor is too weak doesn't mean you shouldn't use it when necessary.
Finally, for fun, I'd like to point you to some macro magic:
- Towers of Hanoi solver using the preprocessor (the input is passed as a macro definition: gcc -o hanoy -Dn=6 vanschnitz.c)
- painting over the mechanics of Duff's device to implement coroutines in C
If you have other cool examples, feel free to add them in the comments.