Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does C expand function-like macros at the end of a macro replacement list?

You can iterate over a preprocessor sequence using the following construct:

#define A() B
#define B() A
A()()()()()

Expands to B on most compilers/preprocessor: clang, gcc, tcc, chibicc, SDCC (I couldn't test msvc, because it didn't work on godbolt, but if you want to test it make sure to use the /Zc:preprocessor flag, because otherwise the preprocessor will be non conforment).

Reading 6.10.3.4 seems to suggest, that the expansion of B happens inside A, which would cause the second expansion of A not to happen, rather it would be painted blue, and the expansion would stop.

6.10.3.4 Rescanning and further replacement

After all parameters in the replacement list have been substituted and # and ## processing has taken place, all placemarker preprocessing tokens are removed. The resulting preprocessing token sequence is then rescanned, along with all subsequent preprocessing tokens of the source file, for more macro names to replace.

But Annex J.1 says that whether this is done using nesting or not is unspecified behavior:

When a fully expanded macro replacement list contains a function-like macro name as its last preprocessing token and the next preprocessing token from the source file is a (, and the fully expanded replacement of that macro ends with the name of the first macro and the next preprocessing token from the source file is again a (, whether that is considered a nested replacement (6.10.3).

Ok, fair, so most preprocessor use the non nesting approach, but what allows the following to work?

#define A() B(
#define B() A(
A()))))

Now granted the former will give you an error, for a "unterminated argument list invoking macro 'B'", but wouldn't you expect this to expand to A())), where A is now painted blue, which shouldn't give an error?

And further, you can get rid of the error by detecting the last closing parentheses, showing that this does also not seem to use nesting, which is weird, because where does the standard suggest that this is valid?

There is already a similar question on SO, but I don't see how the answer has anything to do with the question, since the passage quoted is only talking about argument substitution:

6.10.3.1 Argument substitution

After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

Which makes sense, so e.g. in #define A(x) x x x the argument x passed to A would only need to be expanded once in insolation and afterwards the resulting tokens are inserted in place of the occurrences of x in the expansion list.

This also explains the following behavior:

#define STR(x) #x
#define f(x) x
#define F(x) STR(x(23))
F(f) // expands to "f(23)"

So the in isolation part refers to the arguments them self and not what happens in the rescanned, that is detailed in 6.10.3.4, which is my initial standard quote.

So what is going on here, how should I think about the macro expansion process?

like image 784
camel-cdr Avatar asked Nov 14 '22 18:11

camel-cdr


1 Answers

From my reading of DR17, if the ) is joined with the result of expansion on the left of it has been intentionally left unspecified in the standard. The behavior is undefined. Strictly conforming programs shouldn't use this.

Why is the macro-name not painted blue? , https://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_017.html , https://port70.net/%7Ensz/c/c11/n1570.html#6.10.3.4p4

like image 131
KamilCuk Avatar answered Jan 18 '23 08:01

KamilCuk