Chris Frank
Chris Frank

Reputation: 4452

Is this __VA_ARGS__ expansion valid c99?

I am trying to write a function which takes variadic parameters. It has the following prototype:

void foo(const char *name, const char *file, uint32_t line, const char *fmt, ...);

and I call it with the following macro:

#define FOO(name, ...) \
    foo(name, __FILE__, __LINE__, __VA_ARGS__);

From what I understand the following will be valid:

FOO("Example, "Hello %s", "Stack Overflow");

But will the following result in undefined behavior in a standards compliant c99 compiler?

FOO("Example", "Hello Stack Overflow");

My worry is that because the foo is expecting both *fmt as well as ... that a trailing , will be added when there are only two arguments passed to the macro.

Can anyone tell me if the above is valid c99?

EDIT: When I run this with gcc and std=c99 it works, but I am worried that there is silent UB

Thank you!

Upvotes: 1

Views: 170

Answers (2)

Kamajii
Kamajii

Reputation: 1878

As a side node also related to variadic arguments, C mandates that the variadic part of the macro must be supplied at least one argument (which is the case in the original question).

From ISO/IEC 9899:TC2 §6.10.3:4:

If the identifier-list in the macro definition does not end with an ellipsis, the number of arguments (including those arguments consisting of no preprocessing tokens) in an invocation of a function-like macro shall equal the number of parameters in the macro definition. Otherwise, there shall be more arguments in the invocation than there are parameters in the macro definition (excluding the ...).

Also there is a draft addressing this very problem: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2034.htm

Fortunately, most modern compilers either suppress the trailing comma or provide some sort of work around. However, this is compiler specific behaviour.

In the original post the variadic part of the FOO macro is given the string literal "Hello Stack Overflow". Upon expansion of the macro the foo function might be called as follows:

foo("Example", "someFile.c", 42 "Hello Stack Overflow");

This is fine, as long as the foo function by some means is aware that there are no more arguments to expect after the last fixed argument.

Upvotes: 2

Andrew Henle
Andrew Henle

Reputation: 1

Per 6.10.3.1 Argument substitution, paragraph 2 of the C standard:

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.

Thus, __VA_ARGS__ will be replaced with all the tokens passed as arguments to the macro.

So, in your case, given the macro:

#define FOO(name, ...) \
    foo(name, __FILE__, __LINE__, __VA_ARGS__);

called with

FOO("Example", "Hello Stack Overflow");

and the function declaration

void foo(const char *name, const char *file, uint32_t line, const char *fmt, ...);

results in something like

 foo( "Example", "asdf.c", 1234, "Hello Stack Overflow" );

and no undefined behavior in the call to foo. (What happens inside foo() might still be UB.)

Upvotes: 2

Related Questions