Macro expansion is easy to specify and implement, and you can do
a lot of cute tricks with it. Those early designers appear to have been
influenced by experience with assemblers, in which macro facilities
were often the only device available for structuring programs.
The strength of macro expansion is that it knows nothing about
the underlying syntax of the base language, and can be used to extend
that syntax. Unfortunately, this power is very easily abused to
produce code that is opaque, surprising, and a fertile source of
hard-to-characterize bugs.
In C, the classic example of this sort of problem is a macro
such as this:
#define max(x, y) x > y ? x : y
There are at least two problems with this macro. One is that it
can produce surprising results if either of the arguments is an
expression including an operator of lower precedence than > or ?:.
Consider the expression max(a = b,
++c). If the programmer has forgotten that max is a macro, he will be expecting the
assignment a = b and the preincrement
operation on c to be executed before
the resulting values are passed as arguments to max.
But that's not what will happen. Instead, the preprocessor will
expand this expression to a = b > ++c ? a = b :
++c, which the C compiler's precedence rules make it
interpret as a = (b > ++c ? a = b :
++c). The effect will be to assign to a!
This sort of bad interaction can be headed off by coding the
macro definition more defensively.
#define max(x, y) ((x) > (y) ? (x) : (y))
With this definition, the expansion would be ((a = b) > (++c) ? (a = b) : (++c)). This
solves one problem — but notice that c may be incremented twice! There are subtler
versions of this trap, such as passing the macro a function-call with
side effects.
In general, interactions between macros and expressions with
side effects can lead to unfortunate results that are hard to
diagnose. C's macro processor is a deliberately lightweight and
simple one; more powerful ones can actually get you in worse
trouble.
A minor problem, compared to this one, is that macro expansion
tends to screw up error diagnostics. The base language processor
generates its error reports relative to the macro expanded text, not
the original the programmer is looking at. If the relationship
between the two has been obfuscated by macro expansion, the emitted
diagnostic can be very difficult to associate with the actual
location of the error.
This is especially a problem with preprocessors and macros
that can have multiline expansions, conditionally include or exclude
text, or otherwise change line numbers in the expanded text.
Macro expansion stages that are built into a language can do
their own compensation, fiddling line numbers to refer back to the
preexpanded text. The macro facility in
pic(1)
does this, for example. This problem is more difficult to solve when
the macro expansion is done by a preprocessor.