【问题标题】:Compilers and argument order of evaluation in C++C++ 中的编译器和参数求值顺序
【发布时间】:2010-10-11 21:57:52
【问题描述】:

好的,我知道标准规定 C++ 实现可以选择函数的参数评估顺序,但是在实际会影响的情况下,是否有任何实现实际上“利用”这一点程序?

经典例子:

int i = 0;
foo(i++, i++);

注意:我不是在找人告诉我不能依赖评估顺序,我很清楚这一点。我只对是否有任何编译器真的按照从左到右的顺序进行评估感兴趣,因为我的猜测是,如果他们做了很多写得不好的代码会中断(这是正确的,但他们仍然可能会抱怨)。

【问题讨论】:

  • 用不同的编译器自己试试?
  • 在为 c++ 的子集实现解释器时问了自己同样的问题。唷。
  • 请注意foo(i++,i++) 调用未定义的行为,因为i 在没有任何干预任何序列点的情况下递增超过一。
  • clang 使用从左到右的评估。
  • 使用 C++ 时,您应该始终使用静态代码检查器。任何称职的人都会指出这个错误。

标签: c++ operator-precedence


【解决方案1】:

这取决于参数类型、被调用函数的调用约定、架构和编译器。在 x86 上,Pascal 调用约定从左到右评估参数,而在 C 调用约定 (__cdecl) 中,它是从右到左。大多数在多个平台上运行的程序都会考虑调用约定以跳过意外。

如果您有兴趣,Raymond Chen 的博客上有一个很好的article。您可能还想查看 GCC 手册的 Stack and Calling 部分。

编辑:只要我们一头雾水:我的回答不是将其视为语言问题,而是将其视为平台问题。语言标准不保证或偏爱其中一种,而是将其保留为未指定。注意措辞。它并没有说这是未定义的。从这个意义上说,未指定意味着你不能指望的东西,不可移植的行为。我手边没有 C 规范/草案,但它应该类似于我的 n2798 草案(C++)

抽象机的某些其他方面和操作在本国际标准中描述为未指定(例如,函数参数的评估顺序)。在可能的情况下,本国际标准定义了一组允许的行为。这些定义了抽象机器的不确定性方面。因此,对于给定的程序和给定的输入,抽象机的一个实例可以有多个可能的执行顺序。

【讨论】:

  • 为了清楚起见,pascal/cdecl 决定参数是从左到右还是从右到左传递——它们仍然可以被求值 以任意顺序。 (尽管由于注册压力,它们很可能会按照通过的顺序进行评估。)
  • 错误:C 参数从右到左压入堆栈。但是它们的评估顺序是不确定的。
  • @Martin York:我什么时候说订单明确了?
  • @Martin York:我所说的 C 是指 C 调用约定。已编辑帖子。
  • @dirkgently:您的回答说调用约定决定了评估顺序。正如 Martin York 所说,这不是真的,它只定义了它们被压入堆栈的顺序。
【解决方案2】:

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

【讨论】:

  • 哦?很高兴知道这一点。谢谢。目前我使用的唯一编译器是 VC 08 和 ICC 11。我可能也应该开始在 GCC 中测试我的代码。
  • ICC 竭尽全力尽可能与 VS 兼容。那些英特尔人的好人:)
  • VS 也不保证评估顺序。他们之前已经改变了评估函数参数的方式,他们没有理由不再这样做。这不是你应该依赖的东西。
【解决方案3】:

我希望大多数现代编译器会尝试交错计算参数的指令,因为 C++ 标准要求它们是独立的,因此没有任何相互依赖关系。这样做应该有助于保持深度流水线 CPU 的执行单元满载,从而提高吞吐量。 (至少我希望一个声称是优化编译器的编译器在给出优化标志时会这样做。)

【讨论】:

    【解决方案4】:

    Read this

    这不是您问题的精确副本,但我的回答(和其他一些人)也涵盖了您的问题。

    编译器可能不仅选择从右到左而且还交错它们有很好的优化原因。

    该标准甚至不保证顺序排序。它保证当函数被调用时,所有参数都已被完全评估。

    是的,我已经看到几个版本的 GCC 正是这样做的。对于您的示例,将调用 foo(0,0) ,之后 i 将是 2。 (我不能给你编译器的确切版本号。那是不久前的事了——但看到这种行为再次出现我不会感到惊讶。这是一种安排指令的有效方式)

    【讨论】:

    • 常见子表达式消除是优化重新排序的主要原因。请记住,参数求值和参数传递是允许不同的,即使许多编译器对它们进行相同的处理。
    【解决方案5】:

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

    我相信从右到左的评估顺序已经非常古老了(从第一个 C 编译器开始)。当然在 C++ 发明之前,C++ 的大多数实现都会保持相同的评估顺序,因为早期的 C++ 实现只是简单地翻译成 C。

    从右到左评估函数参数有一些技术原因。在堆栈架构中,参数通常被压入堆栈。在 C/C++ 中,您可以调用具有比实际指定更多参数的函数——额外的参数被简单地忽略。如果参数从左到右计算,并从左到右推送,则堆栈指针正下方的堆栈槽将保存最后一个参数,并且函数无法获取任何特定参数的偏移量(因为实际推送的参数数量取决于调用者)。

    在从右到左的推送顺序中,堆栈指针正下方的堆栈槽将始终保存第一个参数,下一个槽保存第二个参数,依此类推。参数偏移量对于函数始终是确定性的(可能在其他地方编写并编译到一个库中,与调用它的地方分开)。

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

    【讨论】:

      【解决方案6】:

      我在c++ standards找到了答案。

      第 5.2.2.8 段:

      参数的评估顺序未指定。参数表达式评估的所有副作用在输入函数之前生效。后缀表达式和参数表达式列表的求值顺序是 未指定。

      换句话说,它只取决于编译器。

      【讨论】:

      • 嗯,没有回答问题。
      猜你喜欢
      • 2011-02-25
      • 2019-10-10
      • 2010-09-05
      • 1970-01-01
      相关资源
      最近更新 更多