23

几天前这里有一个关于是否表达的讨论

我 = ++我 + 1

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

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

在同一线程中,我参与了与Johannes Schaub的讨论。据他介绍

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

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

然后他给出了以下解释:

“是的,在 i++ 之后的序列点完成了它之前的所有副作用,但是没有什么可以阻止赋值副作用与 i++ 的副作用重叠。根本问题是赋值的副作用没有指定在之后发生或在评估赋值的两个操作数之前,因此序列点在保护这个方面无能为力:序列点导致偏序:仅仅因为在 i++ 之后和之前有一个序列点并不意味着所有副作用都是有序的关于 i

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

用“粗体”写的声明使我感到困惑。我所知道的:

“在被称为序列点的执行序列中的某些指定点,之前评估的所有副作用都应该是完整的,并且后续评估的副作用应该没有发生。”

因为,逗号运算符还指定执行顺序,所以当我们到达最后一个 i.He(Johannes) 时,i++ 的副作用已被取消)。

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

谢谢!

4

3 回答 3

15

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

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

在我看来,在声明中:

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

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

但是,这个表达式:

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

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

但我也认为所有这一切都足够粗略(而且我对所涉及的细微差别的理解也足够粗略),如果有人能说服我(在任何方面),我不会感到惊讶。

于 2009-12-13T08:52:30.073 回答
6

我相信以下表达式肯定具有未定义的行为。

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

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

这也有未定义的行为。

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

现在,我不太确定这个说法。

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

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

子表达式(i, i++, i)作为确定要存储的值的一部分发生,并且该子表达式在将值存储到 之后包含一个序列点i。我看不出有什么方法不需要在i++确定要存储的值之前完成副作用,因此最早可能出现赋值副作用。

在这个序列点i的值最多被读取一次之后,只是为了确定将存储回的值i,所以最后一部分很好。

于 2009-12-13T10:30:26.897 回答
5

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

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

不错的语言数独,不过。:-)

编辑:这里有更详细的解释。

于 2009-12-14T08:29:42.403 回答