Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

the problem about different treatment to __VA_ARGS__ when using VS 2008 and GCC

I am trying to identify a problem because of an unusual usage of variadic macros. Here is the hypothetic macro:

#define va(c, d, ...) c(d, __VA_ARGS__)
#define var(a, b, ...)  va(__VA_ARGS__, a, b)

var(2, 3, printf, “%d %d %d\n”, 1);

For gcc, the preprocessor will output

printf("%d %d %d\n", 1, 2, 3)

but for VS 2008, the output is

printf, “%d %d %d\n”, 1(2, 3);

I suspect the difference is caused by the different treatment to VA_ARGS, for gcc, it will first expand the expression to va(printf, "%d %d %d\n", 1, 2, 3), and treat 1, 2, 3 as the VA_ARGS for macro va. But for VS 2008, it will first treat b as VA_ARGS for macro va, and then do the expansion.

Which one is correct interpretation for C99 variadic macro? or my usage falls into an undefined behavior?

like image 443
liuliu Avatar asked Apr 04 '10 21:04

liuliu


2 Answers

There is an easy way to deal with this problem:

#define exp(...) __VA_ARGS__
#define va(c, d, ...) c(d, __VA_ARGS__)
#define var(a, b, ...)  exp(va(__VA_ARGS__, a, b))

var(2, 3, printf, “%d %d %d\n”, 1);

This will do the trick on VS 2008 and it won't affect gcc

like image 157
monkeyman Avatar answered Nov 03 '22 02:11

monkeyman


Look into the ISO/IEC 9899:1999, chapter 6.10.3.1. It states that:

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.

So in va the first argument c has one preprocessing token being __VA_ARGS__, which, according to this paragraph, must be macro replaced before being substituted to c (which still does not give the answer as to which compiler is right)

But later:

An identifier __VA_ARGS__ that occurs in the replacement list shall be treated as if it were a parameter, and the variable arguments shall form the preprocessing tokens used to replace it.

According to the first fragment, first the arguments to var are identified. They are: 2, 3, printf, “%d %d %d\n” and 1. Now argument substitution takes place. This means that parameters from var's replacement list are taken and replaced. However, the second fragment states, that identifier __VA_ARGS__ is to be treated as a parameter, so it must be substituted to printf, “%d %d %d\n”, 1. Now there are no macros inside parameters, so no further substitution takes place, resulting in the expansion of var(2, 3, printf, “%d %d %d\n”, 1); into va(printf, “%d %d %d\n”, 1, 2, 3);. Because va is a macro, it is also expanded, giving the result of printf(“%d %d %d\n”, 1, 2, 3);. Now if you take VS 2008's reasoning, how can it identify the arguments to va, if one of them is __VA_ARGS__ from var, and can contain many arguments? Well, it treats __VA_ARGS__ as an argument to va, which in my opition is wrong, as, according to fragment one, argument substitution takes place only after the arguments for invocation have been identified.

So it seems to me, that in var the preprocessor has to first identify the arguments to the invocation of the macro va, and then start expanding va. And this means that probably gcc is right here.

It is also shown in C preprocessor and concatenation how processing tokens are macro-replaced in order, until there are no identifiers which can be further expanded as macros, or the preprocessor spots a recursion and terminates expansion.

like image 38
Michał Trybus Avatar answered Nov 03 '22 01:11

Michał Trybus