I have understood how the conversion rules are for expression but I cannot find where or sum up to conversion related to function arguments. Any help please ?
If the argument matches a declared parameter from a function prototype, it is converted to that type.
Otherwise, there is either no function prototype or the prototype ends in ... and the argument matches the ellipsis, and the argument undergoes the default argument promotions.
Example:
void f(char, int, ...);
signed char a = 'x';
f( 25.8 // converted (truncated) to char matching parameter
, a // converted (promoted) to int matching parameter
, a // default-promoted to int (not matching any parameter)
, 1.f) // default-promoted to double (not matching any parameter)
Basically this is obsolete, functions have prototypes. But it lives on in variadic functions where you don't have a proper prototype. In reality these seem to be used almost exclusively for printf() style format strings, and most compilers have special case code built in to warn about non-matching types.
But shorts and chars are promoted to int, and floats are promoted to double. long integers remain long (so they need the %ld format), there's also long long (%lld format).
Related
To the best of my knowledge, in C auto type conversion or type promotion takes place when I try to store small data type in larger data type. For example, int is promoted to float or double.
But in the code below, I am getting garbage value:
#include <stdio.h>
#include<stdarg.h>
double average(int num,...)
{
va_list a_list;
double sum=0.0;
va_start(a_list,num);
for(int i=0;i<num;i++)
sum+=va_arg(a_list,double);
va_end(a_list);
return sum/num;
}
int main()
{
printf("%.2lf\n",average(5,4.0,7,8,9,10));
printf("%.2lf\n",(4.0+7+8+9+10)/5);
return 0;
}
The first printf function does not give correct output, whereas the second one gives.
When arguments are passed for the ... portion of a function’s parameters, only the default argument promotions are performed. These include the integer promotions and converting float to double. They do not convert integer types to floating-point types.
The conversions that occur in passing arguments to functions with declared parameter types rely on knowing the types in advance, when the program is compiled, so that the arguments can be passed in a specified way. Each computing platform has some rules for passing arguments, called the application binary interface [ABI]. A common characteristic of ABIs is that integer and pointer arguments are passed in general registers of the processor (or on a stack), while floating-point arguments are passed in floating-point registers.
When there is a variable argument list and va_arg is used, the called function is requesting arguments during program execution. When the called function requests an integer value with va_arg, va_arg attempts to get it from where an integer argument would be passed. When the called function requests a floating-point value, va_arg attempts to get it from where a floating-point argument would be passed. The called function has no way of knowing whether you passed an integer argument or a floating-point argument, so it has no way of directing va_arg to fetch the value from where you passed the argument rather than from where it expects the argument to be. You must pass the correct type of arguments.
va_list has non-existent type safety so if you mix several different types in the variable argument list, you need to keep track of their types somehow. That's what printf does with the format string.
The argument list of a variadic function is promoted according to an oddball rule called the default argument promotions (C17 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. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined.
Meaning that small integer types get promoted to int, but constants like 5 that are already int do not get promoted, particularly not to a floating point type. So your code has undefined behavior as per the above quote, since the function treats int arguments as double.
Variadic functions are not recommended to be used for any purpose, they are almost certainly the wrong solution to any problem you are having. Use arrays and/or structs instead.
#include<stdio.h>
int main() {
long a = 9;
printf("a = %d",a);//output is 9 but with a warning 'expecting long int'
}
Why can't long here be converted to int?
Variadic functions in general and printf family in particular, are odd special cases. They are notorious for their non-existent type safety, so if you pass the wrong type or use the wrong format string, you invoke undefined behavior and anything can happen.
In your case, most likely int and long happen to have the same representation so the program works despite the warning.
In the case of a regular function though, there is a kind of "demotion" taking place if you pass a larger integer type to a function expecting a smaller one. When this happens, you trigger a conversion from the larger type to the smaller, which is well-defined. (The result will however be compiler-specific if you mix types of different signedness.)
Compilers tend to warn against such implicit conversions, so it is better to do the conversion explicitly with a cast.
Because that's the way variadic functions behave in C language. printf is just a function from the standard library and has no special processing. It is declared as
int printf(const char restrict *fmt, ...);
And the standard (n1256 draft for C99) says (emphasize mine):
6.5.2.2 Function calls...
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...
7 ... 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.
That means that on all parameters to printf, float are converted to double and integer promotions occur on integral arguments.
And in 6.3.1.1 Arithmetic operands / Boolean, characters, and integers
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 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, the value is converted to an int;
otherwise, it is converted to an unsigned int. These are called the integer
promotions.48) All other types are unchanged by the integer promotions.
So as long has a rank greater than int, it is left unchanged by an integer promotion, and the format shall be adapted to accept a long:
long a = 9;
printf("a = %ld",a);
Following passes a long, yet printf() expects an int due to "%d". Result: undefined behavior (UB). If int and long are the same size that UB might look like everything is OK, or it may fail. It is UB.
long a = 9;
printf("a = %d",a); // UB
Why can't long here be converted to int?
long can be converted, yet code did not direct that like the below code.
printf("a = %d", (int) a); // OK
Can integer promotion happen in the reverse order ... in variadic functions like printf()?
Integer promotions do not happen in the reverse order unless code explicitly down-casts or assigned to a narrower type.
There are cases where demotion will appear to be true with printf().
The below promotes sc to int as it is passed to printf(). printf() will take that int and due to "%hhd" will convert it to signed char and then print that numeric value.
signed char sc = 1;
printf("a = %hhd", cs); // prints 1
The below passes i to printf() as an int. printf() will take that int and due to "%hhd" will convert it to signed char and then print that numeric value. So in this case it looks like i was demoted.
int i = 0x101;
printf("a = %hhd", i); // prints 1
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?
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 <stdarg.h> doesn't mention argument promotion. The rule is stated in the section on function calls, N1570 6.5.2.2 paragraph 7:
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.
The book says the following on page 45:
Since an argument of a function call is an expression, type conversions also take place when arguments are passed to functions. In the absence of a function prototype, char and short become int, and float becomes double. This is why we have declared function arguments to be int and double even when the function is called with char and float.
I don't understand what the last sentence there is saying. Can someone lead me in the right direction?
We can see that happen here. According to cplusplus.com, this is the declaration of printf():
int printf(const char * format, ...);
The ... means this function can take an unknown number of parameters of unspecified types, and because it is unspecified, the standardization of numeric types to int and double happens to all printf() parameters except the first, that was specified.
Example:
char x = 10;
short y = 100;
int z = 1000;
printf("Values of char is %d, short is %d, and int is %d", x, y, z);
All those integer types are automatically recasted to int when passed to printf(). We can see that as %d works for all of them.
Note that types bigger than double and int are not converted, such as long int, long double, long long etc. Those types are 64-bits.
When you use a prototype for a function in C (ansi C, as original K&R specification didn't define parameters this way) you declare a formal parameter as having a type. When you match it in an actual expression, two things can happen:
The formal parameter and the actual expression are the same type. In this case, every thing is fine and the expression value is used to initialize the parameter prior to call the function.
The formal parameter and the actual expression are not the same type. In that case, the compiler tries to do automatic type conversion if possible from the type of the actual expression to the formal parameter type.
In case no prototype is found, the rules you put above mandate, so chars and shorts get promoted to int values, and al the floating type values get promoted to double.
The last phrase in your quoted paragraph tells you that in some example (not shown) that types are being used for formal parameters to make sure actual expressions get converted to the types of formal parameters.
int main()
{
int x,y;
int z;
char s='a';
x=10;y=4;
z = x/y;
printf("%d\n",s); //97
printf("%f",z); //some odd sequence
return 0;
}
in the above piece of code the char s is automatically converted to int while printing due to the int type in control string, but in the second case the int to float conversion doesn't happen. Why so?
In both cases the second argument is promoted to int. This is how variadic functions work, and has nothing to do with the format string.
The format string is not even looked at by the compiler: it's just an argument to some function. Well, a really helpful compiler might know about printf() and might look at the format string, but only to warn you about mistakes you might have made. In fact, gcc does just that:
t.c:9: warning: format ‘%f’ expects type ‘double’, but argument 2 has type ‘int’
It is ultimately your responsibility to ensure that the variadic arguments match the format string. Since in the second printf() call they don't, the behaviour of the code is undefined.
Functions with variable number of arguments follow the rule of the default argument promotion. Integer promotion rules are applied on arguments of integer types and float arguments are converted to double.
printf("%d\n",s);
sis a char and is converted to int.
printf("%f",z);
z is already an int so no conversion is performed on z
Now the conversion specifier f expects a double but the type of the object after the default argument promotion is an int so it is undefined behavior.
Here is what C says on arguments of library functions with variable number of arguments
(C99, 7.4.1p1) "If an argument to a function has [...] a type (after promotion) not expected by a function with variable number of arguments, the behavior is undefined."
The char is not being promoted to int due to the control string. The char is working as an int because all data that is less than 4 bytes when passed to printf is bumped up to 4 bytes, which is the size of an int, because of the cdecl calling convention of variadic functions (the point of this is so that the data that comes next will be aligned on a 4-byte boundary on the stack).
printf is not type-safe and has no idea what data you really pass it; it blindly reads the control string and extracts a certain number of bytes from the stack based on what sequences it finds, and interprets that set of bytes as the datatype corresponding to the control sequence. It doesn't perform any conversions, and the reason you are getting some wierd printout is because the bits of an int are being interpreted as the bits of a float.
due to the int type in control string
That is incorrect. It is being converted because shorter int types are promoted to int by the var_args process. Int types are not converted to float types because the va/preprocessor doesn't know what formats are expected.