好的,我知道标准规定 C++ 实现可以选择评估函数的参数的顺序,但是在实际影响程序的情况下,是否有任何实现实际上“利用”这一点?
经典示例:
int i = 0;
foo(i++, i++);
注意:我不是在找人告诉我不能依赖评估顺序,我很清楚这一点。我只对是否有任何编译器真的按照从左到右的顺序进行评估感兴趣,因为我的猜测是,如果他们做了很多写得不好的代码会中断(这是正确的,但他们仍然可能会抱怨)。
好的,我知道标准规定 C++ 实现可以选择评估函数的参数的顺序,但是在实际影响程序的情况下,是否有任何实现实际上“利用”这一点?
经典示例:
int i = 0;
foo(i++, i++);
注意:我不是在找人告诉我不能依赖评估顺序,我很清楚这一点。我只对是否有任何编译器真的按照从左到右的顺序进行评估感兴趣,因为我的猜测是,如果他们做了很多写得不好的代码会中断(这是正确的,但他们仍然可能会抱怨)。
它取决于参数类型、被调用函数的调用约定、架构和编译器。在 x86 上,Pascal调用约定从左到右评估参数,而在 C 调用约定 ( __cdecl ) 中,它是从右到左。大多数在多个平台上运行的程序都会考虑调用约定以跳过意外。
如果您有兴趣,Raymond Chen 的博客上有一篇不错的文章。您可能还想查看 GCC 手册的堆栈和调用部分。
编辑:只要我们分心:我的回答不是将其视为语言问题,而是将其视为平台问题。语言标准不保证或偏爱一个而不是另一个,并将其保留为未指定。注意措辞。它并没有说这是未定义的。从这个意义上说,未指定意味着你不能指望的东西,不可移植的行为。我手边没有 C 规范/草案,但它应该类似于我的 n2798 草案(C++)
抽象机的某些其他方面和操作在本国际标准中描述为未指定(例如,函数参数的评估顺序)。在可能的情况下,本国际标准定义了一组允许的行为。这些定义了抽象机器的不确定性方面。因此,对于给定程序和给定输入,抽象机的一个实例可以具有多个可能的执行顺序。
我在c++ 标准中找到了答案。
第 5.2.2.8 段:
参数的评估顺序未指定。参数表达式评估的所有副作用在输入函数之前生效。后缀表达式和参数表达式列表的求值顺序未指定。
换句话说,它仅取决于编译器。
这不是您问题的精确副本,但我的回答(和其他一些人)也涵盖了您的问题。
编译器可能不仅选择从右到左而且还交错它们有很好的优化原因。
该标准甚至不保证顺序排序。它只保证当函数被调用时,所有参数都已被完全评估。
是的,我已经看到几个版本的 GCC 正是这样做的。对于您的示例,将调用 foo(0,0) ,之后 i 将是 2。(我不能给你编译器的确切版本号。那是不久前的事了——但看到这种行为再次出现我不会感到惊讶。这是安排指令的一种有效方式)
所有参数都被评估。订单未定义(根据标准)。但是 C/C++ 的所有实现(我知道)从右到左评估函数参数。 编辑:CLang 是一个例外(见下面的评论)。
我相信从右到左的评估顺序非常古老(从第一个 C 编译器开始)。当然在 C++ 发明之前,C++ 的大多数实现都会保持相同的评估顺序,因为早期的 C++ 实现只是简单地翻译成 C。
从右到左评估函数参数有一些技术原因。在堆栈架构中,参数通常被压入堆栈。在 C/C++ 中,您可以调用具有比实际指定更多参数的函数——额外的参数被简单地忽略。如果参数从左到右计算,并从左到右推送,则堆栈指针正下方的堆栈槽将保存最后一个参数,并且函数无法获取任何特定参数的偏移量(因为实际推送的参数数量取决于调用者)。
在从右到左的推送顺序中,堆栈指针正下方的堆栈槽将始终保存第一个参数,下一个槽保存第二个参数,依此类推。参数偏移量对于函数始终是确定性的(可以写成在别处编译成一个库,与调用它的地方分开)。
现在,从右到左的推送顺序并不强制要求从右到左的评估顺序,但在早期的编译器中,内存是稀缺的。在从右到左的评估顺序中,可以就地使用相同的堆栈(本质上,在评估参数之后——可能是表达式或函数调用!——返回值已经在堆)。在从左到右的评估中,参数值必须单独存储,并以相反的顺序推回堆栈。
上次我在 2007 年看到 VS2005 和 GCC 3.x 在 x86 硬件上的差异。所以这是(是?)很可能的情况。所以我不再依赖评估顺序了。也许现在好多了。
我希望大多数现代编译器会尝试交错计算参数的指令,因为 C++ 标准要求它们是独立的,因此没有任何相互依赖关系。这样做应该有助于保持深度流水线 CPU 的执行单元满载,从而提高吞吐量。(至少我希望一个声称是优化编译器的编译器在给出优化标志时会这样做。)