tags:

views:

128

answers:

5

considering this function

double avg(double v1,double v2,...)
{
    double sum=v1+v2;
    int counter=2;
    double temp;
    va_list pargs;
    va_start(pargs,v2);
    while((temp=va_arg(pargs,double))!=0.0)
    {
     sum+=temp;
     counter++;
    }
    va_end(pargs);
    return sum/counter;
}

This call printf("%lf\n",avg(3.0,4.5,4.5,3.0,0.0)) returns the correct result but if i delete the last parameter 0.0 if prints -321738127312000000000.0000000 but sum and counter have the right values. I kinda don't understand why i have to check that !=0.0 and have the last param 0.0

+3  A: 

if you remove != 0.0 your program does dirty read until it reads a zero'ed memory block.

you have two choices:

  • specify how many arguments you are passing i.e. avg(3, 4.3, 2.0, 3.0);
  • specify a terminator or sentinel i.e. avg(4.3, 2.0, 3.0, 0.0);

EDIT

for the sake of curiosity I tried to alleviate the need of an esplicit terminator using variadic macros:

#define avg(v1, v2, ...) _avg((v1), (v2), __VA_ARGS__, 0.0)

double _avg(double v1,double v2,...) 
{ 
    /* same code, just prefixing function name with _ */

beware:

avg(3.0, 3.0, 0.0, 100.0, 100.0)

yields 3.0, since you are terminating the va_list prematurely. You can try use another "weird" sentinel value...

dfa
Another macro way using C99's compound literals: #define avg(...) _argv(sizeof((double[]){__VA_ARGS__})/sizeof(double), (double[]){__VA_ARGS__})
Johannes Schaub - litb
it might be a good idea to use NaN or infinity as sentinel value (e.g. `NAN` from <math.h>)
Christoph
+1  A: 

Ultimately this has to do with how arguments are passed to functions. On the stack the arguments are just all loaded up in order, but the function has no way of knowing when it's done reading arguments. There's still data on the stack, though.

That's what the test for != 0.0 does, it uses a sentinel value (0) to identify the end of the series. Another way to do this is to pass the number of items in as the first parameter to the function, and then use a for loop to loop over the variable args.

Yuliy
+6  A: 

Because without any other external information, the function has no idea how many arguments were passed in. There are several strategies to solve this problem: include an explicit argument which is the number of extra arguments, use a format string to define the arguments (such as with the printf and scanf family of functions), or use a sentinel value such as 0 to declare the end of the arguments.

In your case, if you omit the sentinel, the function just keeps walking down the stack until it hits a zero value, and depending on what data is on the stack, you could get wildly different results, all incorrect.

Adam Rosenfield
And of course, you can crash your program. :)
Matthew Flaschen
+1  A: 

You need to have a guard value (0.0) and check for it, because the compiler does not necessarily count or delimit parameters when it constructs a stack frame. Therefore you can continue reading (or writing) beyond the list of parameters and into data that holds your return pointer, your local variables, or just about anything else. If you look at your compiler's implementation of va_arg, you will probably find that all it is doing is initializing a pointer just beyond the address of the your variable (v2) and then incrementing it by the size you specify (double). It will happily do this until you get a read violation.

Eric
A: 

As everyone has mentioned your code relies on a sentinel value to know when it has reached the end of the list. I personally think it is inappropriate to use a sentinel for a function such as avg(). I would change the function to explicitly state the number of arguments as the first argument (as dfa has suggested).

It's only OK to use a sentinel value if your domain has a value that is appropriate to use. For example if you are dealing with only positive numbers, then you could use any negative number as the sentinel. However, it makes more sense for avg() to accept the entire domain of floats.

Adam Luter