Variadic Functions - c

#include <stdarg.h>
#include <stdio.h>
void varfun(int n,...){
va_list ptr;
int num;
va_start(ptr,n);
num=va_arg(ptr,int);
printf("\n%d",num);
}
int main(int argc, char **argv){
varfun(3,7.5,-11.2,0.66);
return 0;
}
Look at the above code, i expect the output to be the first variable parameter value casted to int i.e 7.5 casted to int, i.e. 7. But the output is 0.
What is wrong in this?

The va_arg does not convert the argument. It interprets it as the indicated type. And if the types don't match, you invoke Undefined Behaviour.
va_arg(ptr, int); /* take the next 4 bytes from the stack and interpret them as an `int` */
va_arg(ptr, double); /* take the next 8(?) bytes ... and interpret as double */
(int)va_arg(ptr, double); /* ... convert to int */
Also note the cast isn't really needed in your snippet. The compiler will convert automatically
void varfun(int n, ...) {
va_list ptr;
int num;
va_start(ptr, n);
num = va_arg(ptr, double); /* interpret as double, then convert to int */
printf("%d\n",num);
}

Your second argument, 7.5 is of type double, but your call va_arg(ptr, int) is treating it as an int. This means undefined behavior.
A variadic function has to be able to figure out the number and type(s) of the arguments -- and if the caller lies, you're out of luck. printf-like functions do this via the format string; other functions might treat all their arguments as pointers and use a trailing null pointer as a sentinel.

This is not really casting but merely "reading the first few bytes" (*) of a floating point variable and considering it an integer.
You need to read this argument as its actual type, a double, and then casting as an int.
num = (int) va_arg(ptr, double);
(*) Technically, the behavior associated with va_arg(ptr, wrong_type) is undefined. "reading the first few bytes" is a figure of speech, only describing what typically may happen. No serious program should rely on such implementation details.

It won't automatically cast the value 7.5 into an int like that. You'll need to use va_arg to retrieve the value as a double and then do the cast afterwards.
va_arg(ptr,int) is actually expecting the value stored at ptr to be a bitwise representation of an int, and it's not.

Related

Accessing memory address using format specifiers [duplicate]

I'm trying to print out the value of argv at this point in my code, but when I try to compile it, I get
warning: format ‘%p’ expects argument of type ‘void *’, but argument 3 has type ‘char **. I'm not sure what this warning means though. Is %p not meant to be used for argv even though it's a pointer?
int main(int argc, char* argv[])
{
printf("%s%p", "argv = ", argv);
}
So why don't you cast it?
printf("argv = %p\n", (void *)argv);`
The %p pointer is specified (POSIX printf(); C11 §7.21.6.1 The fprintf function):
p — The argument shall be a pointer to void. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.
Since char ** is not a void *, you need to do the explicit conversion. The compiler isn't allowed by the standard to do the conversion to void * for you — the ellipsis notation in the declaration of printf() means that default argument promotions occur, but that affects only the float type and integer types smaller than int (short, char, etc).
On most machines these days, that's a no-op. On the (long obsolete) machine where I learned to program in C, there weren't void * yet (too old for the standard), but the equivalent was char *, and the value of a char * address for a given memory location was different from the pointer value for any other type — the casts were not optional.
Note that I'm assuming you intend to print the value of the address. If you intend to print the contents, then there's more work to be done.
Well, not any pointer (type).
According to C11, chapter §7.21.6.1, for the %p conversion specifier,
p
The argument shall be a pointer to void. The value of the pointer is
converted to a sequence of printing characters, in an implementation-defined
manner.
So, the argument must be of type void * (or, a char*, given they have the same alignment requirements). Any other type of pointers, must be converted through an explicit cast, as there is no default argument promotion for pointers.
Something like
printf("%s%p", "argv = ", (void *)argv);
should do.
In order to print argv and it's address you need to do
int main(int argc, char* argv[])
{
int i;
printf("address of argv = %p\n", (void *)argv);
for (i = 0; i < argc; ++i)
{
printf("%s\n", argv[i]);
}
return 0;
}

data type confusion in C

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

Passing float as argument screws up value

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"
^^

Variadic function (va_arg) doesn't work with float?

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

What are the automatic type promotions of variadic function arguments?

Consider the following code snippet:
#include <stdio.h>
#include <stdarg.h>
void display(int num, ...) {
char c;
int j;
va_list ptr;
va_start(ptr,num);
for (j= 1; j <= num; j++){
c = va_arg(ptr, char);
printf("%c", c);
}
va_end(ptr);
}
int main() {
display(4, 'A', 'a', 'b', 'c');
return 0;
}
The program gives runtime error because vararg automatically promotes char to int, and i should have used int in this case.
What are all types are permitted when I use vararg, how to know which type to use and avoid such runtime errors.
another case that the others forgot to mention are pointer types, critical is NULL in particular. Since this could expand to 0 or (void*)0 (or some other weird things) you will not know if the compiler puts an int or a void* in the list. Since these can have different width, this can lead to annoying bugs.
You can use any standard type with va_arg except char, signed char, unsigned char, short, unsigned short, _Bool, and float. It's possible that an implementation defines additional nonstandard types that also have integer conversion rank lower than int, or likewise nonstandard small floating-point types, but you would not need to be aware of these unless you intend to use them, so for practical purposes the list I gave is complete.
While using va_arg the char is promoted to int. There are other types(the list given by #R..) which are promoted.
so in order to read it as a char you have to do typecast.
like:
c = (char) va_arg(ap, int);
for the complete example please see:
http://en.cppreference.com/w/cpp/language/variadic_arguments

Resources