编译器和C ++中的评估顺序

好的,我知道标准规定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)将被调用,而我将在2之后。 (我不能给你编译器的确切版本号,这是前一阵子 – 但是我不会惊讶地发现这个行为会再次出现,这是一个有效的安排指令的方法)

所有的参数都被评估。 订单没有定义(按照标准)。 但是所有C / C ++的实现(我知道)都是从右到左评估函数参数。 编辑:CLANG是一个例外(见下面的评论)。

我相信从右到左的评估顺序已经很老了(从第一个C编译器开始)。 在C ++发明之前肯定是这样的,C ++的大多数实现将保持相同的评估顺序,因为早期的C ++实现简单地转换为C.

从右到左评估函数参数有一些技术上的原因。 在堆栈体系结构中,参数通常被压入堆栈。 在C / C ++中,可以使用比实际指定的参数更多的参数来调用函数 – 额外的参数被忽略。 如果参数从左到右进行求值,并且从左向右推进,那么位于堆栈指针下方的堆栈槽将保存最后一个参数,函数无法获得任何特定参数的偏移量(因为推送的实际数量取决于调用者)。

按照从右到左的顺序,堆栈指针下面的堆栈槽总是保存第一个参数,下一个槽保存第二个参数等等。参数偏移量对于函数来说总是确定的(可以写入在其他地方编译成图书馆,与所谓的地方分开)。

现在,从右到左的推送顺序并不要求从右到左的评估顺序,但是在早期的编译器中,内存很少。 在从右到左的评估顺序中,可以在原地使用相同的堆栈(基本上,在评估参数之后 – 可以是表达式或函数调用!) – 返回值已经在堆栈)。 在从左到右的评估中,参数值必须单独存储,并按相反顺序推回到堆栈。

我期望大多数现代编译器会尝试交叉计算参数的指令,因为C ++标准要求它们是独立的,因此缺乏相互依赖性。 这样做有助于保持深度流水线的CPU执行单元的完整性,从而提高吞吐量。 (至少我会期望一个自称是优化编译器的编译器会在给出优化标志的时候这样做)。

我上次在2007年的一个x86硬件上看到了VS2005和GCC 3.x之间的差异。所以这是一个很可能的情况。 所以我不再依赖评估顺序。 也许现在好多了。

Interesting Posts