C- IS(X ,Y) (Y ,X)未定义或未指定,如果未指定,它可以计算什么



逗号序列运算符在表达式中引入序列点。我想知道这是否意味着下面的程序避免了不确定的行为。

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

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

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

以表达式:

(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返回为止。因此,没有未定义的行为。

标准的整个第6.5章都提到了评估顺序。我能找到的评估顺序的最佳摘要是标准的(非规范)附件J:

C11附件J

j.1未指定的行为

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

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

至于未定义的行为,逗号操作员什么也没解决。如果首先评估(x++, y),则可以在其他子表达的y++之前立即评估y。由于在两者之间没有序列点两次访问Y,因此除了确定要存储的值以外,该行为是不确定的。更多信息在这里。

因此,您的程序都没有定义和未指定的行为。

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

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

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

但不是:

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

我理解,唯一需要的顺序是(x++)必须在(y)之前出现,并且(y++)必须在(x)之前。

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

标准明确声明哪个操作员引入序列点,+不在其中。因此,可以很好地评估您的表达方式,即禁止的子表达彼此"近距离",而没有序列之间的序列点。

在务实的基础上,存在这种拦截的原因,即对此类操作的操作数的评估可以并行安排,例如,如果处理器有几个管道来进行所讨论的子表达操作。您可以轻松地说服自己,在这样的评估模式下,任何事情都可以发生,结果是无法预测的。

在您的玩具示例中,冲突是可以预见的,但是如果您在子表达中使用指针间接方向,那就不一定是这种情况。那么,您将不知道其中一个子表达是否可能会别名另一个。因此,如果C要允许此类优化,那么基本上不可能做到。

相关内容

  • 没有找到相关文章

最新更新