Notes on the C Preprocessor: Problems with Parentheses

2 minute read

Use of preprocessor macros is full of pitfalls. In fact, its user manual includes a section called “Macro Pitfalls”. One of these pitfalls revolves around operator precedence. Operator precedence problems happen when the expectations about pass-by-value, the default for C functions, are applied to function-like macros. Function-like macros are pass-by-name; the arguments are textually substituted in the macro body. This is especially confusing because both C and preprocessor functions have the same invocation syntax.

Here is an example of what can go wrong:

 #define square(a) a * a
 int z = square(x + y) * 2

This square root method looks perfectly sensible and would be if square were a typical C function. Let’s see what happens when we preprocess this example. We get the following:

int z = x + y * x + y * 2

Notice this will not compute the square of x + y. The preprocessor could care less about operator precedence or any other aspect of the C grammar. It’s just a text-replacement tool. What we want this macro to give us is the following:

int z = ((x + y) * (x + y)) * 2

So the manual advises macro writers to always wrap argument uses in parentheses to avoid this misnesting. Here is the updated example that gives the behavior we want by wrapping the argument a, as well as the entire macro definition, in parentheses:

 #define square(a) ((a) * (a))
 int z = square(x + y) * 2

A more subtle consequence of function-like macro behavior is when they piece together other function-like macros. Take this example from the preprocessor user manual:

 #define twice(x) (2*(x))
 #define call_with_1(x) x(1)
 call_with_1 (twice)
      ==> twice(1)
      ==> (2*(1))

The call_with_1 macro takes any function-like macro and passes 1 to it. While this is contrived example, such chicanery does happen in practice1.

One utterly confusing consequence of text replacement is the possibility of misnesting, where macro definitions may contain unbalanced parentheses.

#define strange(file) fprintf (file, "%s %d",
strange(stderr) p, 35)
    ==> fprintf (stderr, "%s %d", p, 35)

The macro strange contains the open parenthesis, which is balanced by the close parenthesis on line 2 once the macro is expanded. Writing unbalanced parentheses in your code may cause others to think you’re unbalanced.

The bottom-line is that function-like macros are not really functions. The arguments are just textually replaced. Argument values are not computed before substitution. They are instead prescanned, which is a whole other can of worms and a topic for a later installment.

  1. See Figure 3 of the paper on SuperC