Reputation: 783
I have the following function which writes passed arguments to a binary file.
void writeFile(FILE *fp, const int numOfChars, ...)
{
va_list ap;
va_start(ap, numOfChars);
for(int i = 0; i < numOfChars; i++)
{
const char c = va_arg(ap, char);
putc(c, fp);
}
va_end(ap);
}
Upon compiling, I get the following warning from the compiler
warning: second argument to 'va_arg' is of promotable type 'char'; this va_arg
has undefined behavior because arguments will be promoted to 'int' [- Wvarargs]
Now as I understand it, C wants to promote char type to int. Why does C want to do this? Second, is the best solution to cast the int back to a char?
Upvotes: 19
Views: 20249
Reputation: 263627
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);
putc
internally converts its int
argument value to unsigned char
and writes it to fp
.
Upvotes: 16
Reputation: 9353
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);
Upvotes: 21