char type in va_arg
Now as I understand it, C wants to promote char type to int. Why does C want to do this?
Because that's what the standard says. If you pass an integral value with conversion rank smaller than that of int
(e. g. char
, bool
or short
) to a function taking a variable number of arguments, it will be converted to int
. Presumably the reason for this has its roots in performance, where it was (and in fact, often it still is nowadays) better to pass values aligned to a machine word boundary.
Second, is the best solution to cast the int back to a char?
Yes, but you don't really need a cast even, an implicit conversion will do:
char ch = va_arg(ap, int);
Variadic functions are treated specially.
For a non-variadic function, the prototype (declaration) specifies the types of all the parameters. Parameters can be of any (non-array, non-function) type -- including types narrower than int
.
For a variadic function, the compiler doesn't know the types of the parameters corresponding to the , ...
. For historical reasons, and to make the compiler's job easier, any corresponding arguments of types narrower than int
are promoted to int
or to unsigned int
, and any arguments of type float
are promoted to double
. (This is why printf
uses the same format specifiers for either float
or double
arguments.)
So a variadic function can't receive arguments of type char
. You can call such a function with a char
argument, but it will be promoted to int
.
(In early versions of C, before prototypes were introduced, all functions behaved this way. Even C11 permits non-prototype declarations, in which narrow arguments are promoted to int
, unsigned int
, or double
. But given the existence of prototypes, there's really no reason to write code that depends on such promotions -- except for the special case of variadic functions.)
Because of that, there's no point in having va_arg()
accept char
as the type argument.
But the language doesn't forbid such an invocation of va_arg()
; in fact the section of the standard describing . The rule is stated in the section on function calls, N1570 6.5.2.2 paragraph 7:<stdarg.h>
doesn't mention argument promotion
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 description of the va_arg()
macro, 7.16.1.1, says (emphasis added):
If there is no actual next argument, or if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases:
[SNIP]
The "default argument promotions" convert narrow arguments to int
, unsigned int
, or double
. (An argument of an unsigned integer type whose maximum value exceeds INT_MAX
will be promoted to unsigned int
. It's theoretically possible for char
to behave this way, but only in a very unusual implementation.)
Second, is the best solution to cast the int back to a char?
No, not in this case. Casts are rarely necessary; in most cases, implicit conversions can do the same job. In this particular case:
const char c = va_arg(ap, char);
putc(c, fp);
the first argument to putc
is already of type int
, so this is better written as:
const int c = va_arg(ap, int);
putc(c, fp);
The int
value is converted by putc
to unsigned char
and written to fp
.