具有可变数量参数的功能

时间:2009-05-12 18:00:50

标签: c

考虑这个功能

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;
}

此调用printf("%lf\n",avg(3.0,4.5,4.5,3.0,0.0))返回正确的结果,但如果我删除最后一个参数0.0,则会打印-321738127312000000000.0000000,但sum和counter具有正确的值。 我有点不明白为什么我要检查!=0.0并拥有最后一个参数0.0

5 个答案:

答案 0 :(得分:6)

因为没有任何其他外部信息,函数不知道传入了多少个参数。有几种策略可以解决这个问题:包含一个显式参数,即额外参数的数量,使用格式字符串来定义参数(例如使用printfscanf函数族),或使用诸如0的标记值来声明参数的结尾。

在你的情况下,如果你省略了哨兵,那么这个函数就会一直向下走,直到它达到零值,并且根据堆栈上的数据,你可能得到截然不同的结果,都是不正确的。

答案 1 :(得分:3)

如果删除!= 0.0,程序会读取脏读,直到它读取零内存块为止。

你有两个选择:

  • 指定传递的参数数量,即平均值(3,4.3,2.0,3.0);
  • 指定终结者或哨兵,即平均(4.3,2.0,3.0,0.0);

修改

为了好奇,我试图减轻使用esplicit终结器的需要 可变宏:

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

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

要注意:

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

产生3.0,因为你过早地终止了va_list。您可以尝试使用另一个“奇怪的”哨兵值......

答案 2 :(得分:1)

最终这与参数如何传递给函数有关。在堆栈上,参数只是按顺序加载,但是函数无法知道读取参数的时间。但是,堆栈上仍有数据。

这就是!= 0.0的测试,它使用一个标记值(0)来识别系列的结尾。另一种方法是将作为第一个参数的项目数传递给函数,然后使用for循环遍历变量args。

答案 3 :(得分:1)

您需要具有保护值(0.0)并检查它,因为编译器在构造堆栈帧时不一定计算或分隔参数。因此,您可以继续读取(或写入)参数列表以及包含返回指针,局部变量或其他任何内容的数据。如果你看一下你的编译器的va_arg实现,你可能会发现它所做的只是初始化一个超出你的变量地址(v2)的指针,然后按你指定的大小(double)递增它。在您收到读取违规行为之前,它会很乐意这样做。

答案 4 :(得分:0)

正如大家都提到的那样,你的代码依赖于一个标记值来知道它何时到达列表的末尾。我个人认为使用avine()这样的函数的标记是不合适的。我将更改函数以显式声明参数的数量作为第一个参数(如dfa所建议的那样)。

如果您的域名具有适合使用的值,则只能使用sentinel值。例如,如果您只处理正数,那么您可以使用任何负数作为哨兵。但是,avg()接受浮点数的整个域更有意义。