考虑这个功能
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
答案 0 :(得分:6)
因为没有任何其他外部信息,函数不知道传入了多少个参数。有几种策略可以解决这个问题:包含一个显式参数,即额外参数的数量,使用格式字符串来定义参数(例如使用printf
和scanf
函数族),或使用诸如0的标记值来声明参数的结尾。
在你的情况下,如果你省略了哨兵,那么这个函数就会一直向下走,直到它达到零值,并且根据堆栈上的数据,你可能得到截然不同的结果,都是不正确的。
答案 1 :(得分:3)
如果删除!= 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()接受浮点数的整个域更有意义。