cc-preprocessor

C macro expansion order (need to understand why)


Consider the macros:

#define B(y,z) B##y##z
#define C(z) C##z
#define A(x) B(x, C(x))


A(1)

Everywhere I look, people say arguments are replaced first. Which implies this should happen:

A(1) => B(1, C(1)) => B(1, C1) (Argument C(1) first) => B1C1

However, what I get from gcc -E is

B1C(1)

Which implies B() was replaced first.

Note: The C standard section 6.10.3.1 says arguments get expanded first. Also, I dont think 6.10.3.4.2 (name of macro in replacement list) applies here.

(Swapping first 2 lines gives the same result.)

In fact, the following gives me the expected result:

#define B(y,z) B##y##z
#define B_H(y,z) B(y,z)
#define C(z) C##z
#define A(x) B_H(x, C(x))


A(1)

gives B1C1 . Which implies... both macros were simultaneously replaced?

A(1) => B_H(1, C(1)) => B(1, C1) (2 replacements) => B1C1

My question is: what is going on here? What rules are being applied?


Solution

  • The issue here is not order of evaluation/replacement but rather the fact that a parameter adjacent to ## is replaced by the actual argument, not the macro-replaced argument.

    C 2024 6.10.5.2 says:

    … The argument’s preprocessing tokens are completely macro replaced before being substituted as if they formed the rest of the preprocessing file with no other preprocessing tokens being available…

    However, this text is qualified:

    For each parameter in the replacement list that is neither preceded by a # or ## preprocessing token nor followed by a ## preprocessing token, the preprocessing tokens naming the parameter are replaced by a token sequence determined as follows: …

    So A(1) is processed and produces B_H(1, C(1)). Then, in considering B_H, the compiler sees it has parameters y and z, and these appear in the replacement list of B_H as B##y##z. So z is preceded by ##, and therefore the corresponding argument is not macro-replaced. C(1) is sent into the replacement for B_H as is, with no macro processing. So B##y##z uses 1 for y and C(1) for z and concatenates them, producing B1C(1) (which is not a valid preprocessing token, so the behavior is not defined by the C standard).

    (Similar text appears in C 2018 6.10.3.1 but has been expanded in C 2024 to deal with __VA_OPT__.)