Why the C preprocessor is a good thing

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 type MAX (x++, f (y))
  • if one of the arguments is a function f(), but f 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:

If you have other cool examples, feel free to add them in the comments.