5

When writing C macro, there is a trick called "sequence iteration". It looks like as follow:

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
#define FUNCTION(name) void name();
#define FUNCTION_TABLE(seq) CAT(FUNCTION_TABLE_1 seq, _END)
#define FUNCTION_TABLE_1(x) FUNCTION(x) FUNCTION_TABLE_2
#define FUNCTION_TABLE_2(x) FUNCTION(x) FUNCTION_TABLE_1
#define FUNCTION_TABLE_1_END
#define FUNCTION_TABLE_2_END

FUNCTION_TABLE((x) (y) (z) (e))

The sequence, i.e. the argument of FUCTION_TABLE, will be processed one by one. However, as far as I know, a token will not be expanded twice in the same scope. Because it is "painted blue". When FUNCTION_TABLE_2 is expanded, the macro FUNCTION_TABLE_1 has already been painted yet. Why is it still expanded?

Yyao
  • 395
  • 3
  • 17

2 Answers2

3

Function-like macro replacement is a recursive process:

  • The macro is expanded non-recursively first, except for # and ##
  • Then, recursively, any token that isn't the operand of # or ## is expanded
  • # and ## (if any) are applied to the resulting operands
  • Go to 1, until steps 1-3 cause no changes.

(this is somewhat more complicated to describe than implement!). So for your code:

  • FUNCTION_TABLE((x) (y) (z) (e)) expands to CAT(FUNCTION_TABLE_1 (x) (y) (z) (e), _END)
  • CAT(X, _END) expands to PRIMITIVE_CAT(expand(X), _END) . Working out expand(X) now:
    • FUNCTION_TABLE_1 (x) (y) (z) (e) expands to FUNCTION(x) FUNCTION_TABLE_2 (y) (z) (e):
      • FUNCTION(x) expands to void x();
      • FUNCTION_TABLE_2 (y) (z) (e) expands to FUNCTION(y) FUNCTION_TABLE_1 (z) (e)
      • FUNCTION(y) expands to void y();
      • FUNCTION_TABLE_1 (z) (e) expands to FUNCTION(z) FUNCTION_TABLE_2(e)
        • FUNCTION(z) expands to void z();
        • FUNCTION_TABLE_2(e) expands to FUNCTION(e) FUNCTION_TABLE_1
        • FUNCTION(e) expands to void e();
        • Result: void e(); FUNCTION_TABLE_1
        • Result: void z(); void e(); FUNCTION_TABLE_1
      • Result: void y(); void z(); void e(); FUNCTION_TABLE_1
      • Result: void x(); void y(); void z(); void e(); FUNCTION_TABLE_1
  • So, having fully expanded X, let's recap where we are: PRIMITIVE_CAT(void x(); void y(); void z(); void e(); FUNCTION_TABLE_1, _END)
  • This expands to void x(); void y(); void z(); void e(); FUNCTION_TABLE_1_END
  • After rescanning, this gives void x(); void y(); void z(); void e();
M.M
  • 138,810
  • 21
  • 208
  • 365
  • So if there are three macros waiting to be expanded, the whole body will be scanned three times. After every full expansion of a macro, a rescan will take place. Is it right? – Yyao May 17 '17 at 08:10
  • Well...It's hard to understand this processing. `FUNCTION_TABLE_1(x)` is expanded to `void x(); FUNCTION_TABLE_2`. At this moment, `FUNCTION_TABLE_2` is not a macro yet. But after subsitution, `FUNCTION_TABLE_2(y)` is expanded as a macro. It seems that the processor rescanned the result of substitution. – Yyao May 17 '17 at 09:03
  • I believe I've done this right; here's my own way of explaining it, by example: https://stackoverflow.com/a/66130832/1599699 – Andrew Feb 10 '21 at 04:17
1

The idea is that inside a macro expansion all the expansion of parameters will start with the same BLUE-SET.

inside FUNCTION_TABLE(seq) the expansion of ALL parameters inside FUNCTION_TABLE_1 seq will start all the time with BLUE-SET={seq}. So after entering inside the FUNCTION_TABLE_1 the BLUE-SET is added the x, but when this finishes it comes back to the scope of FUNCTION_TABLE where the expansion starts again with the BLUE-SET={seq}.

So, the first time it is expanded FUNCTION_TABLE_1(x) and INSIDE this expansion the BLUE-SET={seq,x} but when the expansion of FUNCTION_TABLE_1 finishes, it comes back to FUNCTION_TABLE and it will expand next FROM this scope the FUNCTION_TABLE_2(y) and INSIDE FUNCTION_TABLE_2 the BLUE-SET={seq, x} again, etc.

alinsoar
  • 15,386
  • 4
  • 57
  • 74
  • Thank you :) But I still have a question. In the processing, `FUNCTION_TABLE_1(x) ` is expanded to `void x(); FUNCTION_TABLE_2`. At this moment, `FUNCTION_TABLE_2` is not a macro yet. But after subsitution, `FUNCTION_TABLE_2(y)` is expanded as a macro. It seems that the processor rescanned the result of substitution. Is there really a rescanning? – Yyao May 17 '17 at 09:01
  • Yes, the preprocessor generates a new stream after each evaluation and it rescans the new stream all the time. The new stream will have preprocessing tokens each with a new BLUE-SET, such that the recursion is not possible. NOTE: the blue set is attached on EACH PREPROCESSING-TOKEN, NOT on EACH STREAM. – alinsoar May 17 '17 at 10:24
  • Thank you very much! It helps me a lot :) – Yyao May 17 '17 at 10:26