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
it 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
此调用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 个解决方案
#1
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.
因为没有任何其他外部信息,函数不知道传递了多少个参数。有几种策略可以解决这个问题:包括一个显式参数,即额外参数的数量,使用格式字符串来定义参数(例如与printf和scanf系列函数一样,或使用诸如0的标记值来声明参数的结尾。
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.
在你的情况下,如果你省略了哨兵,那么这个函数就会一直向下走,直到它达到零值,并且根据堆栈中的数据,你可能得到截然不同的结果,都是不正确的。
#2
if you remove != 0.0 your program does dirty read until it reads a zero'ed memory block.
如果删除!= 0.0,程序会读取脏读,直到它读取零内存块为止。
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);
指定传递的参数数量,即avg(3,4.3,2.0,3.0);
指定终结者或哨兵,即平均值(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:
为了好奇,我试图减少使用可变参数宏的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 _ */
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...
产生3.0,因为你过早地终止了va_list。你可以尝试使用另一个“奇怪的”哨兵价值......
#3
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.
这就是!= 0.0的测试,它使用一个标记值(0)来识别系列的结尾。另一种方法是将作为第一个参数的项目数传递给函数,然后使用for循环遍历变量args。
#4
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.
您需要具有保护值(0.0)并检查它,因为编译器在构造堆栈帧时不一定计算或分隔参数。因此,您可以继续读取(或写入)参数列表以及包含返回指针,局部变量或其他任何内容的数据。如果你看一下你的编译器的va_arg实现,你可能会发现它正在做的就是初始化一个超出你的变量地址(v2)的指针,然后按你指定的大小(double)递增它。在您收到读取违规之前,它会很乐意这样做。
#5
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).
正如大家都提到的那样,你的代码依赖于一个sentinel值来知道它何时到达列表的末尾。我个人认为使用avine()这样的函数使用标记是不合适的。我将更改函数以显式声明参数的数量作为第一个参数(如dfa所建议的那样)。
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.
如果您的域具有适合使用的值,则只能使用sentinel值。例如,如果您只处理正数,那么您可以使用任何负数作为哨兵。但是,avg()接受浮点数的整个域更有意义。
#1
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.
因为没有任何其他外部信息,函数不知道传递了多少个参数。有几种策略可以解决这个问题:包括一个显式参数,即额外参数的数量,使用格式字符串来定义参数(例如与printf和scanf系列函数一样,或使用诸如0的标记值来声明参数的结尾。
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.
在你的情况下,如果你省略了哨兵,那么这个函数就会一直向下走,直到它达到零值,并且根据堆栈中的数据,你可能得到截然不同的结果,都是不正确的。
#2
if you remove != 0.0 your program does dirty read until it reads a zero'ed memory block.
如果删除!= 0.0,程序会读取脏读,直到它读取零内存块为止。
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);
指定传递的参数数量,即avg(3,4.3,2.0,3.0);
指定终结者或哨兵,即平均值(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:
为了好奇,我试图减少使用可变参数宏的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 _ */
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...
产生3.0,因为你过早地终止了va_list。你可以尝试使用另一个“奇怪的”哨兵价值......
#3
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.
这就是!= 0.0的测试,它使用一个标记值(0)来识别系列的结尾。另一种方法是将作为第一个参数的项目数传递给函数,然后使用for循环遍历变量args。
#4
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.
您需要具有保护值(0.0)并检查它,因为编译器在构造堆栈帧时不一定计算或分隔参数。因此,您可以继续读取(或写入)参数列表以及包含返回指针,局部变量或其他任何内容的数据。如果你看一下你的编译器的va_arg实现,你可能会发现它正在做的就是初始化一个超出你的变量地址(v2)的指针,然后按你指定的大小(double)递增它。在您收到读取违规之前,它会很乐意这样做。
#5
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).
正如大家都提到的那样,你的代码依赖于一个sentinel值来知道它何时到达列表的末尾。我个人认为使用avine()这样的函数使用标记是不合适的。我将更改函数以显式声明参数的数量作为第一个参数(如dfa所建议的那样)。
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.
如果您的域具有适合使用的值,则只能使用sentinel值。例如,如果您只处理正数,那么您可以使用任何负数作为哨兵。但是,avg()接受浮点数的整个域更有意义。