是(x ++,y)+(y ++,x)未定义或未指定,如果未指定,它可以计算什么?

时间:2012-12-18 15:12:12

标签: c c99 language-lawyer c11 unspecified-behavior

comma sequence operator在表达式中引入了sequence point。我想知道这是否意味着下面的程序避免了未定义的行为。

int x, y;

int main()
{
  return (x++, y) + (y++, x);
}

如果它确实避免了未定义的行为,它仍然可能未指定,即返回几个可能值中的一个。我认为在C99中,它只能计算1,但实际上,各种版本的GCC将此程序编译为返回2的可执行文件。 Clang生成一个返回1的可执行文件,显然同意我的直觉。

最后,这是在C11中改变了吗?

4 个答案:

答案 0 :(得分:6)

采用表达式:

(x++, y) + (y++, x)

从左到右评估:

x++  // yield 0, schedule increment of x
,    // sequence point: x definitely incremented now
y    // yield 0
y++  // yield 0, schedule increment of y
// explode because you just read from y and wrote to y
// with no intervening sequence point

标准中没有任何内容禁止这样做,因此整个事情都有未定义的行为。

对比这个伪代码:

f() { return x++, y; }
g() { return y++, x; }
f() + g()

根据C99(5.1.2.3/2),对fg的调用自身计为副作用,函数调用运算符在进入函数之前包含一个序列点。这意味着函数执行不能交错。

在“并行评估”模式下:

f()  // arbitrarily start with f: sequence point; enter f
g()  // at the same time, start calling g: sequence point

由于f的执行本身就算作副作用,g()中的序列点会暂停执行,直到f返回。因此,没有未定义的行为。

答案 1 :(得分:5)

标准的第6.5章提到了运营商的评估顺序。我能找到的评估顺序的最佳总结是该标准的(非规范性)附件J:

  

C11 Annex J

     

J.1未指明的行为

     
      
  • 评估子表达式的顺序以及副作用发生的顺序,除非为此指定   function-call(),&&,||,?:和逗号运算符(6.5)
  •   

在您的示例中,您无法知道是否首先计算子表达式(x++, y)(y++, x),因为未指定+运算符的操作数的评估顺序。

对于未定义的行为,逗号运算符什么也没解决。如果首先评估(x++, y),则可以在另一个子表达式的y之前立即评估y++。由于y被访问两次而中间没有序列点,出于其他目的而不是确定要存储的值,行为是未定义的。 More info here.

所以你的程序都有未定义和未指定的行为。

(此外,它具有实现定义的行为,因为int main(),而不是int main(void)不是托管应用程序中明确定义的main版本之一。)

答案 2 :(得分:1)

我的理解是,以下任何评估订单都是合法的:

  • x++, y++, y, x
  • x++, y, y++, x
  • x++, y++, x, y
  • y++, x, x++, y
  • ...

但不是:

  • y, x++, x, y++
  • ...

根据我的理解,唯一的排序是(x++)必须在(y)之前,(y++)必须在(x)之前。

因此,我认为这仍然是未定义的行为。

答案 3 :(得分:1)

标准明确声明了哪些运算符引入了序列点,+不在其中。因此,您的表达式可以通过以下方式进行评估:禁止的子表达式被评估为彼此“接近”,而它们之间没有序列点。

在实用基础上,存在这种阻断的原因,即可以并行地调度对这种操作的操作数的评估,例如,如果处理器具有用于操作所讨论的子表达式的若干流水线。您可以轻松地说服自己,在这种评估模型下,任何事情都可能发生,结果是不可预测的。

在你的玩具示例中,冲突是可预测的,但如果你在子表达式中使用一些指针间接,则不一定是这种情况。然后你不知道其中一个子表达式是否可能为另一个表达式别名。因此,如果它想要允许这种类型的优化,C基本上不会做太多。