Reputation: 507205
This question is about vararg functions, and the last named parameter of them, before the ellipsis:
void f(Type paramN, ...) {
va_list ap;
va_start(ap, paramN);
va_end(ap);
}
I was reading in the C Standard, and found the following restriction for the va_start
macro:
The parameter parmN is the identifier of the rightmost parameter in the variable parameter list in the function definition (the one just before the , ...). If the parameter parmN is declared with the register storage class, with a function or array type, or with a type that is not compatible with the type that results after application of the default argument promotions, the behavior is undefined.
I wonder why the behavior is undefined for the following code
void f(int paramN[], ...) {
va_list ap;
va_start(ap, paramN);
va_end(ap);
}
and not undefined for the following
void f(int *paramN, ...) {
va_list ap;
va_start(ap, paramN);
va_end(ap);
}
The macros are intended to be implementable by pure C code. But pure C code cannot find out whether or not paramN
was declared as an array or as a pointer. In both cases, the type of the parameter is adjusted to be a pointer. The same is true for function type parameters.
I wonder: What is the rationale of this restriction? Do some compilers have problems with implementing this when these parameter adjustments are in place internally? (The same undefined behavior is stated for C++ - so my question is about C++ aswell).
Upvotes: 16
Views: 1162
Reputation: 385295
C++11 says:
[n3290: 13.1/3]:
[..] Parameter declarations that differ only in a pointer * versus an array [] are equivalent. That is, the array declaration is adjusted to become a pointer declaration. [..]
and C99 too:
[C99: 6.7.5.3/7]:
A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. [..]
And you said:
But pure C code cannot find out whether or not
paramN
was declared as an array or as a pointer. In both cases, the type of the parameter is adjusted to be a pointer.
Right, so there's no difference between the two pieces of code you showed us. Both have paramN
declared as a pointer; there is actually no array type there at all.
So why would there be a difference between the two when it comes to the UB?
The passage you quoted...
The parameter parmN is the identifier of the rightmost parameter in the variable parameter list in the function definition (the one just before the , ...). If the parameter parmN is declared with the register storage class, with a function or array type, or with a type that is not compatible with the type that results after application of the default argument promotions, the behavior is undefined.
...applies to neither, as would be expected.
Upvotes: 1
Reputation: 127527
I found another relevant quote, from Dinkumware.
The last parameter must not have register storage class, and it must have a type that is not changed by the translator. It cannot have:
* an array type * a function type * type float * any integer type that changes when promoted * a reference type [C++ only]
So apparently, the problem is precisely that the parameter gets passed in a way different from how it is declared. Interestingly enough, they also ban float and short, even though those should be supported by the standard.
As a hypothesis, it could be that some compilers have problems doing sizeof
correctly on such parameters. E.g. it might be that, for
int f(int x[10])
{
return sizeof(x);
}
some (buggy) compiler will return 10*sizeof(int)
, thus breaking the va_start
implementation.
Upvotes: 2
Reputation: 340366
The restriction against register parameters or function parameters are probably something like:
register
storage class.va_start()
and/or va_arg()
were implemented by adding some fixed amount to the address of paramN
and function pointers were larger than object pointers the calculation would end up with the wrong address for the object va_arg()
returns. This might not seem to be a great way to implement these macros, but there might be platforms that have (or even need) this type of implementation.I can't think of what the problem would be to prevent allowing array parameters, but PJ Plauger says this in his book "The Standard C Library":
Some of the restrictions imposed on the macros defined in
<stdarg.h>
seem unnecessarily severe. For some implementations, they are. Each was introduced, however, to meet the needs of at least one serious C implementation.
And I imagine that there are few people who know more about the ins and outs of the C library than Plauger. I hope someone can answer this specific question with an actual example; I think it would be an interesting bit of trivia.
New info:
The "Rationale for International Standard - Programming Languages - C" says this about va_start()
:
The
parmN
argument tova_start
was intended to be an aid to implementors writing the definition of a conformingva_start
macro entirely in C, even using pre-C89 compilers (for example, by taking the address of the parameter). The restrictions on the declaration of theparmN
parameter follow from the intent to allow this kind of implementation, as applying the & operator to a parameter name might not produce the intended result if the parameter’s declaration did not meet these restrictions.
Not that that helps me with the restriction on array parameters.
Upvotes: 7
Reputation: 51296
I can only guess that the register
restriction is there to ease library/compiler implementation -- it eliminates a special case for them to worry about.
But I have no clue about the array/function restriction. If it were in the C++ standard only, I would hazard a guess that there is some obscure template matching scenario where the difference between a parameter of type T[]
and of type T*
makes a difference, correct handling of which would complicate va_start
etc. But since this clause appears in the C standard too, obviously that explanation is ruled out.
My conclusion: an oversight in the standards. Possible scenario: some pre-standard C compiler implemented parameters of type T[]
and T*
differently, and the spokesperson for that compiler on the C standards committee had the above restrictions added to the standard; that compiler later became obsolete, but no-one felt the restrictions were compelling enough to update the standard.
Upvotes: 1
Reputation: 101615
It's not undefined. Keep in mind that when parameter is declared as int paramN[]
, the actual parameter type will still decay to int* paramN
immediately (which is visible in C++, for example, if you apply typeid
to paramN
).
I must admit that I'm not sure what this bit in the spec is even for, considering that you cannot have parameters of function or array types in the first place (since they will pointer-decay).
Upvotes: 3