What Default Promotions of Types Are There in the Variadic Arguments List

What default promotions of types are there in the variadic arguments list?

Look in the draft n1256 (C99 with Technical corrigenda TC1, TC2, and TC3 included) for 6.5.2.2 Function calls:

For functions without prototype, or parameters corresponding to the ellipsis ..., the default argument promotions are performed.

Those are: Default integer promotions and promotion of float to double.

Default integer promotions: Every integer type of rank less than int is promoted to int or unsigned int.

Default argument promotion in a variadic function

None of the conditions in Paragraph 6 apply to your example. A function without a prototype refers to a function declared using the archaic K&R syntax:

int f();

When you call a function with this type of declaration, all arguments undergo default promotions.

Paragraph 6 also describes other situations where there's a prototype and the types in the call are not compatible to with the types in the prototype, but your types are compatible (they're the same).

Paragraph 7 says that argument conversion is performed for the first two arguments; they're converted to float as specified in the prototype. Since a and b are already float, no conversion is necessary.

The remaining arguments undergo default argument promotion (as described in Paragrah 6), since they correspond to the ellipsis in the prototype. The literals 4.0 and 5.0 have type double, so no promotion is necessary.

better understanding type promotion of variadic parameters in c

"int type should be 4 byte on 32 bit machines and 8 byte on 64 bit machines, is that right?" No. According to the Standard, ints must be at least 16 bits in width (§5.2.4.2.1), but there is no further stipulation.

When you pass a long long int to printf() it is not subject to the integer promotions (§6.3.1.1 2):

The following may be used in an expression wherever an int or unsigned
int may be used:

  • An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to
    the rank of int and unsigned int.
  • A bit-field of type _Bool, int, signed int, or unsigned int.

If an int can represent all values of the original type (as restricted
by the width, for a bit-field), the value is converted to an int;
otherwise, it is converted to an unsigned int. These are called the
integer promotions.58) All other types are unchanged by the integer
promotions
.

If you pass a long double to printf() no conversion is made (§6.5.2.2 6):

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.

The conversion specifiers corresponding to the arguments in the printf() statement have no bearing on these promotions and conversions, except insofar as there will be undefined behavior if the specifiers and the types of their corresponding arguments do not match.

So, the integer promotions are performed, and floats are converted to doubles, but "No other conversions are performed implicitly" (§6.5.2.2 8).

Addressing your edit to the question: "this makes me think that not all parameters are promoted to int." Correct. Only integer types with integer conversion rank "less than or equal to the rank of int and unsigned int" are subject to integer promotion. It is simpler for floating point types; floats are promoted to double. That is all.

It may be worth pointing out that, according to §6.2.5 10, there are three real floating point types, float, double, and long double. The values which may be held by a float are a subset of the values which may be held by a double, which are in turn a subset of the values which may be held by a long double. Hence, there is no possibility of promotion for long double types.

Further, according to §6.3.1.1 1:

The rank of long long int shall be greater than the rank of long int,
which shall be greater than the rank of int, which shall be greater
than the rank of short int, which shall be greater than the rank of
signed char.

So there is no possibility of a long long int or long int ever being promoted to int or unsigned int.

As for your final concern that promotion of a short to an int may, in some implementation, be impossible without losing some bits, note that §6.2.5 8 guarantees that an int must be able to contain a short, since the conversion rank of an int must be greater than that of a short:

For any two integer types with the same signedness and different
integer conversion rank (see 6.3.1.1), the range of values of the type
with smaller integer conversion rank is a subrange of the values of
the other type.

I want default argument promotion's example

For historical reasons, functions in C can be declared the old way, without a prototype, as:

type function()

or the new way, with a prototype, as:

type function(type parameter)
type function(type parameter, type parameter)
type function(type parameter, type parameter, type parameter)
… and so on

Thus, a function declaration in which the types of the parameters are given is said to have a prototype. This affects preparation of the arguments when calling a function. With a prototype, the arguments are converted to the declared types of the parameters. Without a prototype, the arguments are converted to default types, using rules called the default argument promotions. (Also, if a function is just being declared, not defined, the names of the parameters can be omitted, leaving a prototype that just lists types, as in type function(type, type, type).)

Because of the old grammar, to declare a function with a prototype that says it has no parameters, you need to explicitly say void: type function(void). (This is different in C++, which does not have to support the old grammar. In C++, type function() is a prototype with no parameters.)

Additionally, a prototype can specify that there are variable arguments by putting ,... after one or more regular parameters:

type function(type parameter,...)

In this case, the first arguments are converted to the parameter types, but the arguments that correspond to the ... are converted using the default argument promotions. printf is declared this way.

The default argument promotions are:

  • Integers narrower (technically, of lesser rank) than int are promoted to int if it can represent all the values of the source type or unsigned int otherwise.
  • float arguments are converted to double.

There is also some finickiness about bit-fields in the default argument promotions, which I cannot say has ever arisen in code for me.

History

In the old C grammar, a function would be defined with:

type function(name0, name1, name2)
type name0;
type name1;
type name2;

and it would be declared without a prototype, as with type function(). This means the caller did not know the actual types of the parameters. But you could not just pass a char value for a char argument or a short value for a short argument, because C, in trying to be flexible so it could work on many types of computers, had rules about char and short values being promoted to int in expressions. Additionally, character constants like 'X' have type int, not char, so, if somebody called a function with foo('X'), the compiler would not know if foo really wanted just a char rather than an int. So the default argument promotions were made to match the integer promotions.

Later versions of C fixed this by providing a way to declare the argument types in declarations visible to the caller, so the arguments always match the parameters (and the compiler has more information it can use to provide error messages). But the old grammar still has to be supported so that old code can be compiled.

More

The phrase in the C standard, “the expression that denotes the called function,” is used because you can call functions through pointers, not just their names. For example, we can write:

    int (*FunctionPointer)() = (int (*)()) printf;

and then call printf using FunctionPointer("Hello, world.\n");. Then “the expression that denotes the called function” is FunctionPointer, and it does not have a prototype even though printf does. (There is no good reason to do this with the printf function, but some esoteric code may do some unsavory things.)

You can initially declare a function without a prototype:

int foo();

and later add a prototype:

int foo(float x, char *y);

The compiler will merge the declarations, and the resulting foo will have a prototype.

Default argument promotions in C function calls

Upvoted AProgrammer's answer—those are the real goods.

For those of you who are wondering why things are this way: in the dark ages before 1988, there was no such thing as a function prototype in classic "K&R" C, and the default argument promotions were instituted because (a) there were essentially "free", as it costs no more to put a byte in a register than to put a word in a register, and (b) to cut down on potential errors in parameter passing. That second reason never quite cut it, which was why the introduction of function prototypes in ANSI C was the single most important change ever in the C language.

As to when default promotions kick in: default argument promotions are used exactly when the expected type of the argument is unknown, which is to say when there's no prototype or when the argument is variadic.

Sized integers and promotions in varargs functions

To determine if some type becomes int, unsigned or stays the same type when passed to a function with ..., code could use _Generic(). (C99 or later)

By using *1, this mimics the usual promotions seen with f(format, ...).

Now your printf-like function can determine if the argument was promoted to int, unsigned or left as-is.

#include <stdio.h>
#define PromoType(v) _Generic(v*1, \
unsigned : "unsigned", \
int : "int", \
default: "no change" \
)

int main(void) {
int16_t i16;
puts(PromoType(i16));
uint16_t u16;
puts(PromoType(u16));
int32_t i32;
puts(PromoType(i32));
uint32_t u32;
puts(PromoType(u32));
int64_t i64;
puts(PromoType(i64));
uint64_t u64;
puts(PromoType(u64));
return 0;
}

Output

int
int
int
unsigned
no change
no change

Do enum types undergo default argument promotion?

Yes, default argument promotion can happen depending on the underlying type chosen for an enum.

From section 6.7.2.2 of the C standard regarding enumeration specifiers:

Each enumerated type shall be compatible with char, a signed integer
type, or an unsigned integer type. The choice of type is
implementation-defined, but shall be capable of representing the
values of all the members of the enumeration.
The enumerated type is
incomplete until immediately after the } that terminates the list of
enumerator declarations, and complete thereafter

So an implementation may choose to use a type smaller than int as the underlying type, and if so then it is subject to default argument promotions. GCC in particular will do this if you specify the -fshort-enums flag.

And if that's the case, you can't use that enum as the last named argument in a variadic function. From section 7.16.1.4p4 regarding va_start:

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.



Related Topics



Leave a reply



Submit