在C/C++
中,是否有一个固定的顺序来评估函数的参数?我的意思是,标准说什么?是left-to-right
还是right-to-left
?
我从书中得到了令人困惑的信息。
是否有必要使用function call
实施stack only
。 C/C++
标准对此有何看法?
答案 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
在这里有异常。
对于普通函数foo
,foo(bar(x++), baz(++x))
的求值顺序是不确定的。正确!但是,printf
作为省略号函数的求值顺序更为明确。
事实上,标准库中的printf
没有有关已发送到的参数数量的信息。它只是试图从字符串占位符中找出变量的数量。即从字符串中的百分比运算符(%
)数开始。 C编译器开始将参数从最右边推向左边。字符串的地址作为最后一个参数传递。没有确切的参数数目信息,printf
评估最后一个地址(字符串),并开始用堆栈中相应地址的值替换%
(从左到右)。也就是说,对于如下所示的printf
;
{
int x = 0;
printf("%d %d %f\n", foo(x), bar(x++), baz(++x));
}
评估顺序为:
x = 1
调用;返回值被压入堆栈,x = 1
调用;返回值被压入堆栈,x = 2
调用;返回值被压入堆栈,现在,printf
没有有关已发送到的参数数量的信息。此外,如果未在编译中发出-Wall
,则编译器甚至不会抱怨参数数量不一致。那是;该字符串可以包含3个%
,但printf
行中的参数数目可以是1、2、3、4、5,甚至可以只包含字符串本身而没有任何参数。
说,该字符串有3个占位符,您已经发送了5个参数(printf("%d %f %s\n", k1, k2, k3, k4, k5)
)。如果关闭了编译警告,则编译器将不会抱怨参数过多(或占位符数量不足)。知道堆栈地址,printf
将;