简体   繁体   English

具有可变数量参数的功能

[英]Function with variable number of parameters

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. 此调用printf("%lf\\n",avg(3.0,4.5,4.5,3.0,0.0))返回正确的结果,但如果我删除最后一个参数0.0则打印-321738127312000000000.0000000 ,但sum和counter具有正确的值。 I kinda don't understand why I have to check that !=0.0 and have the last param 0.0 我有点不明白为什么我要检查!=0.0并且最后一个参数为0.0

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. 因为没有任何其他外部信息,函数不知道传递了多少个参数。有几种策略可以解决这个问题:包括一个显式参数,即额外参数的数量,使用格式字符串来定义参数(例如与printfscanf系列函数一样),或使用诸如0之类的sentinel值来声明参数的结尾。

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

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 ie avg(3, 4.3, 2.0, 3.0); 指定传递的参数数量,即avg(3,4.3,2.0,3.0);
  • specify a terminator or sentinel ie avg(4.3, 2.0, 3.0, 0.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. 产生3.0,因为你过早地终止了va_list。 You can try use another "weird" sentinel value... 你可以尝试使用另一个“奇怪的”哨兵价值......

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

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. 您需要具有保护值(0.0)并检查它,因为编译器在构造堆栈帧时不一定计算或分隔参数。 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). 如果你看一下你的编译器的va_arg实现,你可能会发现它所做的只是初始化一个超出你的变量地址(v2)的指针,然后按你指定的大小(double)递增它。 It will happily do this until you get a read violation. 在您收到读取违规之前,它会很乐意这样做。

As everyone has mentioned your code relies on a sentinel value to know when it has reached the end of the list. 正如大家都提到的那样,你的代码依赖于一个sentinel值来知道它何时到达列表的末尾。 I personally think it is inappropriate to use a sentinel for a function such as avg(). 我个人认为使用avine()这样的函数的标记是不合适的。 I would change the function to explicitly state the number of arguments as the first argument (as dfa has suggested). 我将更改函数以显式声明参数的数量作为第一个参数(如dfa所建议的那样)。

It's only OK to use a sentinel value if your domain has a value that is appropriate to use. 如果您的域具有适合使用的值,则只能使用sentinel值。 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. 但是,avg()接受浮点数的整个域更有意义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 C ++中函数的可变参数数量 - Variable number of parameters in function in C++ 将具有可变数量参数的函数中的文本与数字连接 - Concatenate text with numbers in a function with variable number of parameters C中参数数量可变的函数的奇数行为 - Odd behavior of function with variable number of parameters in C 用可变数量的参数求和函数中的复数 - sum complex numbers in function with a variable number of parameters 如何从类似的函数调用具有可变数量的参数的函数? - How to call a function with variable number of parameters from a similar function? 如何使用可变数量的参数调用函数? - How do I call a function with a variable number of parameters? 为需要可变数量参数的函数编写替换(c编程) - writing a replacement for a function which takes a variable number of parameters (c programming) 具有double类型的可变数字参数的函数的奇怪输出 - Strange output for function with variable number parameters of type double 在纯 C 中,如何将可变数量的参数传递给函数? - In pure C how can I pass a variable number of parameters into a function? 如何通过可变数量的参数传递函数中的参数列表? - How to pass list of arguments in function with variable number of parameters?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM