I have a variadic function which takes a float parameter. Why doesn't it work?
va_arg(arg, float)
Parameters of functions that correspond to ... are promoted before passing to your variadic function. char and short are promoted to int, float is promoted to double, etc.
6.5.2.2.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.
The reason for this is that early versions of C did not have function prototypes; parameter types were declared at the function site but were not known at the call site. But different types are represented differently, and the representation of the passed argument must match the called function's expectation. So that char and short values could be passed to functions with int parameters, or float values could be passed to functions with double parameters, the compiler "promoted" the smaller types to be of the larger type. This behavior is still seen when the type of the parameter is not known at the call site -- namely, for variadic functions or functions declared without a prototype (e.g., int foo();).
As #dasblinkenlight has mentioned, float is promoted to double.
It works fine for me:
#include <stdio.h>
#include <stdarg.h>
void foo(int n, ...)
{
va_list vl;
va_start(vl, n);
int c;
double val;
for(c = 0; c < n; c++) {
val = va_arg(vl, double);
printf("%f\n", val);
}
va_end(vl);
}
int main(void)
{
foo(2, 3.3f, 4.4f);
return 0;
}
Output:
3.300000
4.400000
Related
I wondering why the compiler let this pass and is giving the right output, although sqrt() from its prototype normally should only get an double value as argument:
In C99 the declaration of the prototype is:
double sqrt (double x);
#include <stdio.h>
#include <math.h>
int main (void)
{
int i = 9;
printf("\t Number \t\t Square Root of Number\n\n");
printf("\t %d \t\t\t %f \n",i, sqrt(i));
}
Output:
Number Square Root of Number
9 3.000000
Why does the compiler not throw a warning at least and the given output is right, if I´m giving the sqrt() function an int as argument?
Is this crossing into Undefined Behavior?
I´m using gcc.
The Question was already asked twice for C++, but not for C, so my question is up for C.
I provide the links to the questions for C++ anyway:
Why does sqrt() work fine on an int variable if it is not defined for an int?
Why is sqrt() working with int argument?
This is not undefined behavior.
The function is defined to accept an argument of type double. Because the type of the argument is known, you can pass an int because it may be implicitly converted to a double. It's the same as if you did:
int i = 4;
double d = i;
The rules for conversion of function arguments are spelled out in section 6.5.2.2p7 of the C standard regarding the function call operator ():
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
In contrast, if you passed an int to printf when the format string expects a double, i.e.:
printf("%f\n", 4);
Then you have undefined behavior. This is because the types of the arguments are not known at compile time so the implicit conversion can't happen.
The following code works:
int main(void)
{
float f = get_float();
int i = round(f*100);
printf("%i\n", i);
}
Yet, error generated if coding this way:
printf("%i\n", round(1.21*100));
Output says round(1.21*100) is float. So, then why
int i = round(f*100);
is fine?
When you do
int i = round(f*100);
you convert the result of the double function round. The converted result is stored in the int variable i, which can be used with the format "%i" as it expects an int argument.
When you pass the double result of round directly as an argument to a format that expects an int you have mismatching format and argument types. That leads to undefined behavior.
No conversion is made in the call to printf, and no conversion can be made since the code inside the printf function doesn't know the actual type of the argument. All it knows is the format "%i". All possible type-information is lost for variable-argument functions.
This is because of the behavior of automatic type casting. In printf, automatic typecasting does not work. When you say %i, it simply expects integer, it can not convert double to integer and then print.
In assignment operation, double is converted to integer first and then it is assigned to left operand of the = operator. I hope this helps.
This is a bit of duplication, but maybe helps for a better understanding:
round() has the following prototype:
double round(double x);
so it returns double.
There is an implicit conversion from double to int in C, so writing
int i = round(f*100);
will convert the result of round() to int.
If you have a function that expects an int, e.g.
void printMyNumber(int number)
{
printf("%i\n", number);
}
you can call it like this:
printMyNumber(round(f*100));
and the implicit conversion works as expected, because the compiler sees both types (the return type from round() and the expected argument type of printMyNumber()).
The reason this doesn't work with printf() is that the prototype of printf() looks like this:
printf(const char *format, ...);
so, except for the first argument, the types of the arguments are unknown. Therefore, whatever you pass is passed without any conversion (except for default argument promotions). Of course, you could use a cast to achieve an explicit conversion instead:
printf("%i\n", (int) round(f*100)); // <- this is fine
At $6.5.2.2.6 the C11 standard:
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. If the function is defined with a type that does not
include a prototype, and the types of the arguments after promotion
are not compatible with those of the parameters after promotion, the
behavior is undefined, except for the following cases: ...
What does that means - I really can't understand it (especially the first part). From what I can however it means that defining a function like this:
void func(int a, int b, ...)
{
}
And then calling it is undefined behavior which I think is silly.
The situation is as follows: You can declare a function without a parameter list and call this function:
int main(void)
{
extern void f(); // no parameter list!
char c = 'x';
f(c, 1UL, 3.5f);
}
In this situation, the arguments are default-promoted: The first argument is promoted to either int or unsigned int (depending on the platform), the second remains unsigned long, and the third is promoted to double.
When the program is linked, some translation unit needs to contain the definition of the function. The definition always contains a parameter list, even if it's empty (but an empty parameter list in the definition means that the function takes no parameters, unlike in the declaration-that-is-not-a-definition above, where it just means that no information is provided about the parameters):
void f(int, unsigned long, double)
{
// ...
}
The standardese you quoted now says that the behaviour is undefined if the parameter types in this definition are not compatible with the promoted types of the call, or if the parameter list ends with an ellipsis.
As a corollary, it follows that if you want to use a function with variable arguments (using the facilities of <stdarg.h> to access the arguments), you must declare the function with a prototype:
extern void f(int, ...); // prototype (containing ellipsis)
f(c, 1UL, 3.5f);
Now c is converted to int because the first parameter is typed, and the second and third arguments are default-promoted just as before because they are passed as part of the ellipsis. The definition of f must now use the same declaration. If you will, passing arguments in a way that the <stdarg.h> facilities can access may require advance knowledge from the compiler, so you have to provide the parameter list before making the call.
The wording is a bit confusing. The whole paragraph is talking about the case where no prototype has been declared for the function at the time it is called, so the section you highlighted is for the case where no prototype is declared when the function is called, but a prototype is used when the function is defined. Here is an example:
int main(int argc,char** argv)
{
f(3.0f); /* undefined behavior */
g(3.0); /* undefined behavior */
}
int f(float v)
{
return v;
}
int g(double v,...)
{
return v;
}
In this example, when f is called, no prototype has been declared, so 3.0f is promoted to a double. However, the function is later defined with a prototype which takes a float instead of a double, so the behavior is undefined.
Likewise for g, the behavior is undefined because the elipses are used in the prototype of the definition.
In my main function, I use the following code
float f = 32.0;
func("test string %f", f);
func (these are all example names) is declared as following
void func(const char *str, ...);
In my implementation of this function, I use a union called all_types to obtain the value of the arguments that are passed
union all_types
{
void *v;
CLObject *obj;
char *s;
long l;
char c;
float f;
int i;
double d;
};
and then give a value to that union like this
union all_types *o = calloc(1, sizeof(union all_types));
while ((o->v = va_arg(list, void *)) != NULL)
Now, when I know the argument is a float, the value for it will be very strange (I set a breakpoint to figure it out). The i and l values on the union will be 32, as they should. However, the f value is some weird number like 0.00000000000000000000000000000000000000000013592595. Does anyone know why I am getting this behavior? This function works for every other type of object I have tested.
The va_arg macro's second argument is the actual type of the actual argument. No conversion takes place as a result of the va_arg invocation. If you don't know the actual type of the actual argument, you're out of luck because there is no way to find out.
Note that default argument conversions do take place in the call itself, so it is impossible to receive a float, char or unsigned short. (The float will be converted to double and the other two to int or unsigned int, depending.)
This is why printf formats make you specify the type of the argument, except for float.
What you are doing invokes undefined behavior, variadic functions will convert floats to double and the undefined behavior comes in because void * is not compatible with double and so you can have no expectation as to the result. We can see this by going to the draft C99 standard section 7.15.1.1 The va_arg macro which says:
[...]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,[...]
The correct way to do this would be:
o->d = va_arg(list, double)
and you have the format specifier so this should be possible:
"test string %f"
^^
here is my snippet of code:
float square_root(x)
float x;
{
.......
}
int main(){
printf("Square_root 2 = %f\n", square_root(4));
}
When I pass number 4.0 to the square_root() function, x parameter inside the function is 4.0000000 so its ok.
But when I pass just 4 (like in example), x variable inside the function becomes 1.976262583365e-323#DEN
Why does that happen?
You're using the old style of function declaration, where the argument types are listed separately. See C function syntax, parameter types declared after parameter list
As you are passing an int, it's not being converted to float by default. Your function, though, is interpreting the argument as a float, which gives undesirable results.
The modern approach is to include the argument type in the function declaration, which will allow your int argument to be automatically converted to a float.
float square_root(float x)
{
.......
}
There is default argument promotion with non-prototype functions and no conversion to the type of the parameter as with prototypes. So basically your int of value 4 is interpreted as a float instead of being converted to a float.
Use a prototyped definition instead:
float square_root(float x)
{
.......
}
to have the argument at the function call converted to a float.
Also note that old-style function definitions are an obsolescent C feature and they should be avoided.