序列点和部分顺序

时间:2009-12-13 08:29:32

标签: c++ c expression language-lawyer

几天前有一个关于表达式

的讨论here
  

i = ++ i + 1

调用UB (未定义的行为)与否。

最后得出的结论是它调用UB,因为'i'的值在两个序列点之间变化不止一次。

我参与了同一个帖子中与Johannes Schaub的讨论。根据他的说法

  

i =(i,i ++,i)+1 ------(1)/ *也调用UB * /

我说过(1)没有调用UB,因为前面的子表达式的副作用被逗号运算符','在i和i ++之间以及i ++和i之间清除。

然后他给出了以下解释:

  

“是的,在i ++完成之前的所有副作用之后的序列点,但没有任何东西可以阻止赋值副作用与i ++的副作用重叠。潜在的问题是赋值的副作用是未指定在赋值的两个操作数的评估之后或之前发生,因此序列点在保护它时无法做任何事情:序列点引发部分顺序:仅仅因为在i ++之后和之前有一个序列点意味着所有副作用都按照i 排序。

     

另外,请注意,仅序列点没有任何意义:评估的顺序不是由代码形式决定的。它由语义规则决定。在这种情况下,没有语义规则说明何时在评估其操作数的操作数或子表达式时发生赋值副作用“。

用“大胆”写的声明使我感到困惑。据我所知:

“在执行序列中称为序列点的某些特定点,先前评估的所有副作用都应完整,并且不会产生后续评估的副作用。”

因为,逗号运算符也指定了执行顺序,当我们到达最后一个i时,i ++的副作用被取消了。如果没有指定评估顺序,那么(Johannes)会是正确的(但是如果是逗号运算符已明确指定)。

所以我只想知道(1)是否调用UB?有人能给出另一个有效的解释吗?

谢谢!

4 个答案:

答案 0 :(得分:15)

C标准说明了关于赋值运算符(C90 6.3.16或C99 6.5.16赋值运算符):

  

更新左操作数存储值的副作用应发生在前一个和下一个序列点之间。

在我看来,在声明中:

i=(i,i++,i)+1;

赋值运算符的序列点'previous'将是第二个逗号运算符,'next'序列点将是表达式的结尾。所以我要说表达式不会调用未定义的行为。

但是,这个表达式:

*(some_ptr + i) = (i,i++,i)+1;

会有未定义的行为,因为赋值运算符的2个操作数的求值顺序是未定义的,在这种情况下,而不是在赋值运算符的副作用发生时的问题,问题是你不知道是否左手柄操作数中使用的i的值将在右手侧之前或之后进行评估。在第一个例子中没有出现这种评估问题的顺序,因为在该表达式中,i的值实际上并未在左侧使用 - 赋值运算符感兴趣的是“左值 - “i

但我也认为这一切都足够粗略(而且我对所涉及的细微差别的理解已经足够粗略),如果有人能够说服我(无论是否计数),我都不会感到惊讶。

答案 1 :(得分:6)

我认为以下表达式肯定有未定义的行为。

i + ((i, i++, i) + 1)

原因是逗号运算符指定括号中子表达式之间的序列点,但未指定该序列中+的左手操作数的评估发生的位置。一种可能性是在i++周围的序列点之间,这违反了5/4,因为i被写入两个序列点之间,但在相同的序列点之间也被读取两次,而不仅仅是确定值要存储,还要确定+运算符的第一个操作数的值。

这也有不确定的行为。

i += (i, i++, i) + 1;

现在,我对此声明不太确定。

i = (i, i++, i) + 1;

尽管适用相同的原则,i必须“评估”为可修改的左值,并且可以随时进行,但我不相信它的是永远的阅读作为其中的一部分。 (或者是否存在表达式违反导致UB的另一个限制?)

子表达式(i, i++, i)作为确定要存储的值的一部分发生,并且该子表达式在将值存储到i之后包含序列点。在确定要存储的值之前,我认为这不会要求i++的副作用完成,因此也就是最早可能发生分配副作用的点。

此序号之后i的值最多只读取一次,并且仅用于确定将存储回i的值,因此最后一部分没问题。

答案 2 :(得分:5)

i=(i,i++,i)+1 ------ (1) /* invokes UB as well */

它不会调用未定义的行为。 i++的副作用将发生在评估下一个序列点之前,后面用逗号表示,也在分配之前。

不错的语言数独。 : - )

编辑:有一个更精细的解释here

答案 3 :(得分:-1)

我在开始时对约翰内斯(litb)的陈述感到困惑,但他提到了:

i = (i, ++i, i) +1
  

<约翰>
  如果&lt; a&gt;是赋值,是一个增量。 :s:是序列点,然后可以在序列点之间按顺序对副作用进行排序:(i :s: i++< a ><n> :s: i) + 1。标量i的值在此处的第一个和第二个序列点之间改变了两次。赋值和增量发生的顺序是未指定的,并且因为它们之间没有序列点,所以它们彼此之间甚至不是原子的。这是这些副作用的未指定顺序所允许的允许排序。 / p>      

这与(i++, i++)不同,因为两个子表达式的评估顺序是从左到右,在它们之间的序列点,前一个评估的增量应该是完整的,下一个增量应该是还没有发生。这强制了两个序列点之间i的值没有变化,这使得(i++, i++)有效   &LT; /约翰内斯&GT;

这让我觉得litb提到的序列无效,因为根据C99:

  

6.5.16.1(2)在简单赋值(=)中,右操作数的值被转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值。

即。在赋值副作用之前需要知道右操作数的值(修改存储在与左操作数对应的对象中的值)

  

6.5.17(2)逗号运算符的左操作数被计算为void表达式;评估后有一个序列点。然后评估右操作数;结果有其类型和价值。

即。需要评估逗号操作的最右边操作数,以了解逗号表达式的值和类型(以及我的示例中右操作数的值)。

因此,在这种情况下,赋值副作用的“前一个序列点”实际上是最右边的逗号操作。 Johannes提到的可能序列无效。

如果我错了,请纠正我。