2

I had this question while reading C++ Standard, but it basically just references C Standard, so I guess this question applies to both languages.

From [cstdarg.syn]

If the parameter parmN is of a reference type, or of a type that is not compatible with the type that results when passing an argument for which there is no parameter, the behavior is undefined.

I dont understand the rule about "compatible" types. Compatible types are pretty much the same types in C, so what does this rule means exactly? parmN cant be float, bool, char, short?

int add_nums(short count, ...) 
{
    int result = 0;
    std::va_list args;
    va_start(args, count); // undefined behavior ?
    for (short i = 0; i < count; ++i) {
        result += va_arg(args, int);
    }
    va_end(args);
    return result;
}

Also, what is the reasoning behind this rule? I get how parmN cant be reference, but I dont see how its type is related to types of variadic arguments.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
klezki
  • 41
  • 5
  • 2
    If you can, basically forget this and use variadic templates, which are type safe. – lorro Dec 05 '22 at 22:29
  • Sorry for the knee-jerk reaction to the C tag with the C++ code. This is a reasonable place to use both tags. – user4581301 Dec 05 '22 at 22:30
  • If you want to write this in C++, you do it like `auto add_nums(auto... vals) { return (vals + ...); }` – NathanOliver Dec 05 '22 at 22:33
  • *I get how parmN cant be reference* -- really? Can you explain it to me? I mean, I'm sure there's a rationale, but it's not clear to me what that would be. – John Bollinger Dec 05 '22 at 23:26
  • @John Bollinger, see [this](https://stackoverflow.com/questions/12371450/how-are-variable-arguments-implemented-in-gcc); from what I understand, in most implementations, `va_start` takes address of `paramN` and uses it to get the next arg. References are not required to have address in C++ [dcl.ref](http://eel.is/c++draft/dcl.ref#4), and if we just take an address of a ref we just get address of the referenced object, so we cant use it in `va_start` like that – klezki Dec 06 '22 at 20:35
  • @lorro yep you are right, but for some reason i just like reading the standard, finding this things no one actually use, and thinking why are they made that way – klezki Dec 06 '22 at 20:48
  • Looks like a bug in the C++ standard to me. There is no notion of compatible types in C++. What types are compatible in C is largely irrelevant. – n. m. could be an AI Dec 19 '22 at 10:17

2 Answers2

1

The rule regarding types has to do with promotion of arguments.

For variadic arguments, passed arguments of type float are promoted to double and integer arguments with a type smaller than int are promoted to int or unsigned int. This means that va_arg cannot expect arguments of these types, otherwise you trigger undefined behavior.

This behavior with regard to variadic functions is documented in section 6.5.2.2p7 of the C11 standard:

If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

And the term default argument promotions is defined in 6.5.2.2p6:

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • _The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter._ - according to this, `parmN` is the last declared parameter, so it shouldnt be affected by default argument promotions. [cstdarg.syn] clause from the question is about undefined behavior after `va_start`, not `va_args`. So thats exactly whats confusing - how wrong type of `parmN` can trigger UB after `va_start`, if it doesnt even undergo default argument promotions – klezki Dec 06 '22 at 20:24
  • @klezki The last **declared** parameter, which in this case is `count`. – dbush Dec 06 '22 at 20:25
0

Have a look at:

In the C reference there is a good example for what is meant by the (confusing) statement:

int add_nums(int count, ...);
int sum = add_nums(2, 'c', true); // add_nums is called with three ints: (2, 99, 1)

And the C++ reference makes it very clear, that:

When a variadic function is called, after lvalue-to-rvalue, array-to-pointer, and function-to-pointer conversions, each argument that is a part of the variable argument list undergoes additional conversions known as default argument promotions:

...

Only arithmetic, enumeration, pointer, pointer to member, and class type arguments (after conversion) are allowed. However, ... (some other stuff)

Erdal Küçük
  • 4,810
  • 1
  • 6
  • 11
  • 1
    Yes and no. Yes, the default argument promotions are (part of) what C++ is talking about there, but if one consults the C language spec about this then one needs to be aware that the restrictions on the argument associated with `parmN` are all C++, not drawn from C at all. In C, the default argument promotions apply to all the variable arguments, but not to the one associated with `parmN` (or other prototyped parameters). – John Bollinger Dec 05 '22 at 23:19
  • @JohnBollinger Somehow i can not wrap my head around _"the default argument promotions apply to all the variable arguments, but not to the one associated with parmN"_. What are all variable arguments and those associated with parmN? Could you be more elaborate on this? – Erdal Küçük Dec 05 '22 at 23:39
  • The "variable arguments" are those that do not correspond to declared parameters. `parmN` is the last-declared [named] parameter. Thus, in the OP's example, `parmN` is `count`, and the variable arguments for any given call to that function are the second and all subsequent ones. – John Bollinger Dec 06 '22 at 13:26
  • @JohnBollinger Thank you very much. It is clear to me now. – Erdal Küçük Dec 06 '22 at 17:20