功能参数评估顺序

时间:2012-03-05 11:54:30

标签: c++ c

C/C++中,是否有一个固定的顺序来评估函数的参数?我的意思是,标准说什么?是left-to-right还是right-to-left? 我从书中得到了令人困惑的信息。

是否有必要使用function call实施stack onlyC/C++标准对此有何看法?

5 个答案:

答案 0 :(得分:31)

C和C ++是两种完全不同的语言;不要假设同样的规则总是适用于两者。但是,在参数评估顺序的情况下:

C99:

6.5.2.2函数调用
...
10函数指示符的评估顺序,实际参数和   实际参数中的子表达式未指定,但有一个序列点    在实际通话之前。

[修改] C11(草案):

6.5.2.2函数调用
...
10在评估函数指示符和实际值之后有一个序列点 参数但在实际调用之前。调用函数中的每个评估(包括 其他函数调用)在其他方式之前或之后没有特别排序 被调用函数体的执行是相对于不确定地排序的 执行被调用的函数。 94)
...
94)换句话说,函数执行不会相互“交错”。

C ++:

5.2.2函数调用
...
8参数的评估顺序未指定。参数表达式评估的所有副作用都会生效  在输入功能之前。后缀表达式和参数表达式列表的评估顺序是   不确定的。

两种标准都没有要求使用硬件堆栈来传递函数参数;这是一个实现细节。 C ++标准使用术语“展开堆栈”来描述从try块到 throw-expression 的路径上自动创建的对象的调用析构函数,但就是这样。最流行的体系结构通过硬件堆栈传递参数,但它不是通用的。

[修改

  

我从书中得到了令人困惑的信息。

这并不是最令人惊讶的,因为很容易就90%写的关于C的书只是 crap

虽然语言标准对于学习 C或C ++来说不是一个很好的资源,但是对于这样的问题很方便。官方™标准文件需要花钱,但有些草稿可以在线免费获取,并且应该足以满足大多数用途。

最新的C99草案(自原始出版物以来有更新)可用here。最新出版前的C11草案(去年正式批准)可供here使用。可以使用C ++语言的公开可用草案here,尽管它明确声明某些信息不完整或不正确。

答案 1 :(得分:7)

保持安全:标准将其留给编译器来确定参数的计算顺序。因此,您不应该依赖特定的订单。

答案 2 :(得分:3)

  

在C / C ++中,有一个固定的顺序来评估函数的参数。我的意思是标准说的是从左到右还是从右到左。我从书中得到了令人困惑的信息。

不,在C和C ++中,函数参数(以及任何表达式中的两个子表达式)的评估顺序是未指定的行为。用简单的英语表示最左边的参数可以先评估,也可以是最右边的参数,你不知道哪个顺序适用于特定的编译器

示例:

static int x = 0;

int* func (int val)
{
  x = val;
  return &x;
}

void print (int val1, int val2)
{
  cout << val1 << " " << val2 << endl;
}

print(*func(1), *func(2));

这段代码非常糟糕。它依赖于打印参数的评估顺序。它将打印“1 1”(从右到左)或“2 2”(从左到右)和我们无法知道哪个。该标准唯一保证的是,在调用print()之前,对func()的调用都已完成。

解决此问题的方法是要注意订单未指定,并编写不依赖于评估顺序的程序。例如:

int val1 = *func(1);
int val2 = *func(2);
print(val1, val2); // Will always print "1 2" on any compiler.

  

是否有必要仅使用堆栈实现函数调用。 C / C ++标准对此有何看法。

这被称为“调用约定”,并且根本没有标准指定。如何传递参数(和返回值)完全取决于实现。它们可以通过CPU寄存器或堆栈传递,也可以通过其他方式传递。调用者可以是负责在堆栈上推送/弹出参数的人,或者函数可以负责。

函数参数的评估顺序仅与调用约定有些关联,因为在调用函数之前进行了评估。但另一方面,某些编译器可以选择将最右边的参数放在CPU寄存器中,其余的都放在堆栈中,作为一个例子。

答案 3 :(得分:0)

仅仅代表 C 语言,函数参数内的评估顺序取决于编译器。来自 Brian Kernighan C编程语言 Dennis Ritchie ;

  

类似地,评估函数参数的顺序不是   指定,所以声明

     

printf("%d %d\n", ++n, power(2, n)); /*WRONG */

     

可以使用不同的编译器生成不同的结果,   取决于在调用电源之前n是否递增。该   解决方案,当然是写

     

++n;

     

printf("%d %d\n", n, power(2, n));

答案 4 :(得分:0)

据我所知,函数printf在这里有异常。

对于普通函数foofoo(bar(x++), baz(++x))的求值顺序是不确定的。正确!但是,printf作为省略号函数的求值顺序更为明确。

事实上,标准库中的printf没有有关已发送到的参数数量的信息。它只是试图从字符串占位符中找出变量的数量。即从字符串中的百分比运算符(%)数开始。 C编译器开始将参数从最右边推向左边。字符串的地址作为最后一个参数传递。没有确切的参数数目信息,printf评估最后一个地址(字符串),并开始用堆栈中相应地址的值替换%(从左到右)。也就是说,对于如下所示的printf

{
    int x = 0;
    printf("%d %d %f\n", foo(x), bar(x++), baz(++x));
}

评估顺序为:

  1. x增加1,
  2. 函数baz用x = 1调用;返回值被压入堆栈,
  3. 功能栏通过x = 1调用;返回值被压入堆栈,
  4. x增加1,
  5. 函数foo用x = 2调用;返回值被压入堆栈,
  6. 字符串地址被压入堆栈。

现在,printf没有有关已发送到的参数数量的信息。此外,如果未在编译中发出-Wall,则编译器甚至不会抱怨参数数量不一致。那是;该字符串可以包含3个%,但printf行中的参数数目可以是1、2、3、4、5,甚至可以只包含字符串本身而没有任何参数。

说,该字符串有3个占位符,您已经发送了5个参数(printf("%d %f %s\n", k1, k2, k3, k4, k5))。如果关闭了编译警告,则编译器将不会抱怨参数过多(或占位符数量不足)。知道堆栈地址,printf将;

  1. 处理堆栈中的第一个整数宽度并将其打印为整数(不知道是否为整数),
  2. 处理堆栈中的下一个双精度宽度并将其打印为双精度(不知道是否为双精度),
  3. 将堆栈中的下一个指针宽度作为字符串地址进行处理,并将尝试在该地址处打印字符串,直到找到字符串终止符(提供的地址指向的是该进程的名称;否则会引发分段错误)。
  4. 并忽略其余参数。