简体   繁体   English

是什么让 i = i++ + 1; 在 C++17 中合法吗?

[英]What made i = i++ + 1; legal in C++17?

Before you start yelling undefined behaviour, this is explicitly listed in N4659 (C++17)在你开始大喊未定义的行为之前,这在N4659 (C++17) 中明确列出

  i = i++ + 1;        // the value of i is incremented

Yet in N3337 (C++11)然而在N3337 (C++11)

  i = i++ + 1;        // the behavior is undefined

What changed?发生了什么变化?

From what I can gather, from [N4659 basic.exec]据我所知,来自[N4659 basic.exec]

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值是无序的。 [...] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. [...] 运算符的操作数的值计算在运算符的结果的值计算之前进行排序。 If a side effect on a memory location is unsequenced relative to either another side effect on the same memory location or a value computation using the value of any object in the same memory location, and they are not potentially concurrent, the behavior is undefined.如果一个内存位置的副作用相对于同一内存位置的另一个副作用或使用同一内存位置中任何对象的值的值计算是未排序的,并且它们不是潜在的并发,则行为是未定义的。

Where value is defined at [N4659 basic.type]其中定义在[N4659 basic.type]

For trivially copyable types, the value representation is a set of bits in the object representation that determines a value , which is one discrete element of an implementation-defined set of values对于可简单复制的类型,值表示是对象表示中的一组位,用于确定,该是实现定义的值集的一个离散元素

From [N3337 basic.exec]来自[N3337 basic.exec]

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值是无序的。 [...] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. [...] 运算符的操作数的值计算在运算符的结果的值计算之前进行排序。 If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.如果对标量对象的副作用相对于对同一标量对象的另一个副作用或使用同一标量对象的值的值计算而言是未排序的,则行为未定义。

Likewise, value is defined at [N3337 basic.type]同样,值定义在[N3337 basic.type]

For trivially copyable types, the value representation is a set of bits in the object representation that determines a value , which is one discrete element of an implementation-defined set of values.对于可简单复制的类型,值表示是对象表示中的一组位,用于确定,该是实现定义的值集的一个离散元素。

They are identical except mention of concurrency which doesn't matter, and with the usage of memory location instead of scalar object , where它们是相同的,除了提到无关紧要的并发性,以及使用内存位置而不是标量对象,其中

Arithmetic types, enumeration types, pointer types, pointer to member types, std::nullptr_t , and cv-qualified versions of these types are collectively called scalar types.算术类型、枚举类型、指针类型、成员类型指针、 std::nullptr_t和这些类型的 cv 限定版本统称为标量类型。

Which doesn't affect the example.这不会影响示例。

From [N4659 expr.ass]来自[N4659 expr.ass]

The assignment operator (=) and the compound assignment operators all group right-to-left.赋值运算符 (=) 和复合赋值运算符都从右到左分组。 All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand.所有都需要一个可修改的左值作为它们的左操作数,并返回一个引用左操作数的左值。 The result in all cases is a bit-field if the left operand is a bit-field.如果左操作数是位域,则所有情况下的结果都是位域。 In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.在所有情况下,赋值顺序都在左右操作数的值计算之后,赋值表达式的值计算之前。 The right operand is sequenced before the left operand.右操作数排在左操作数之前。

From [N3337 expr.ass]来自[N3337 expr.ass]

The assignment operator (=) and the compound assignment operators all group right-to-left.赋值运算符 (=) 和复合赋值运算符都从右到左分组。 All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand.所有都需要一个可修改的左值作为它们的左操作数,并返回一个引用左操作数的左值。 The result in all cases is a bit-field if the left operand is a bit-field.如果左操作数是位域,则所有情况下的结果都是位域。 In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.在所有情况下,赋值顺序都在左右操作数的值计算之后,赋值表达式的值计算之前。

The only difference being the last sentence being absent in N3337.唯一的区别是 N3337 中没有最后一句。

The last sentence however, shouldn't have any importance as the left operand i is neither "another side effect" nor "using the value of the same scalar object" as the id-expression is a lvalue.然而,最后一句不应该有任何重要性,因为左操作数i既不是“另一个副作用”也不是“使用相同标量对象的值”,因为id 表达式是左值。

In C++11 the act of "assignment", ie the side-effect of modifying the LHS, is sequenced after the value computation of the right operand.在 C++11 中,“赋值”的行为,即修改 LHS 的副作用,在右操作数的值计算之后进行排序。 Note that this is a relatively "weak" guarantee: it produces sequencing only with relation to value computation of the RHS.请注意,这是一个相对“弱”的保证:它仅产生与 RHS 的值计算相关的排序。 It says nothing about the side-effects that might be present in the RHS, since occurrence of side-effects is not part of value computation .它没有说明 RHS 中可能存在的副作用,因为副作用的发生不是价值计算的一部分。 The requirements of C++11 establish no relative sequencing between the act of assignment and any side-effects of the RHS. C++11 的要求在赋值行为和 RHS 的任何副作用之间没有建立相对顺序。 This is what creates the potential for UB.这就是为 UB 创造潜力的原因。

The only hope in this case is any additional guarantees made by specific operators used in RHS.在这种情况下,唯一的希望是 RHS 中使用的特定运营商做出的任何额外保证。 If the RHS used a prefix ++ , sequencing properties specific to the prefix form of ++ would have saved the day in this example.如果 RHS 使用前缀++ ,则特定于++前缀形式的测序属性将在本示例中节省一天的时间。 But postfix ++ is a different story: it does not make such guarantees.但是 postfix ++是另一回事:它没有做出这样的保证。 In C++11 the side-effects of = and postfix ++ end up unsequenced with relation to each other in this example.在 C++11 中, =和后缀++的副作用在这个例子中最终彼此之间没有顺序。 And that is UB.那就是UB。

In C++17 an extra sentence is added to the specification of assignment operator:在 C++17 中,在赋值运算符的规范中添加了一个额外的句子:

The right operand is sequenced before the left operand.右操作数排在左操作数之前。

In combination with the above it makes for a very strong guarantee.与上述相结合,它提供了非常强大的保证。 It sequences everything that happens in the RHS (including any side-effects) before everything that happens in the LHS.它在 LHS 中发生的所有事情之前对 RHS 中发生的所有事情(包括任何副作用)进行排序。 Since the actual assignment is sequenced after LHS (and RHS), that extra sequencing completely isolates the act of assignment from any side-effects present in RHS.由于实际分配是LHS(和 RHS)之后排序的,额外的测序将分配行为与 RHS 中存在的任何副作用完全隔离。 This stronger sequencing is what eliminates the above UB.这种更强的排序消除了上述 UB。

(Updated to take into account @John Bollinger's comments.) (更新以考虑@John Bollinger 的评论。)

You identified the new sentence你确定了新句子

The right operand is sequenced before the left operand.右操作数排在左操作数之前。

and you correctly identified that the evaluation of the left operand as an lvalue is irrelevant.并且您正确地确定左操作数作为左值的评估是无关紧要的。 However, sequenced before is specified to be a transitive relation.但是, sequential before被指定为传递关系。 The complete right operand (including the post-increment) is therefore also sequenced before the assignment.因此,完整的右操作数(包括后增量)也在赋值之前排序。 In C++11, only the value computation of the right operand was sequenced before the assignment.在 C++11 中,只有右操作数的值计算在赋值之前被排序。

In older C++ standards and in C11, definition of the assignment operator text ends with the text:在旧的 C++ 标准和 C11 中,赋值运算符文本的定义以文本结尾:

The evaluations of the operands are unsequenced.操作数的评估是无序的。

Meaning that side-effects in the operands are unsequenced and therefore definitely undefined behavior if they use the same variable.这意味着操作数中的副作用是未排序的,因此如果它们使用相同的变量,则绝对是未定义的行为。

This text was simply removed in C++11, leaving it somewhat ambiguous.这段文字在 C++11 中被简单地删除了,让它有些含糊不清。 Is it UB or is it not?是UB还是不是? This has been clarified in C++17 where they added:这已在 C++17 中澄清,他们补充说:

The right operand is sequenced before the left operand.右操作数排在左操作数之前。


As a side note, in even older standards, this was all made very clear, example from C99:作为旁注,在更旧的标准中,这一切都非常清楚,例如来自 C99:

The order of evaluation of the operands is unspecified.操作数的计算顺序未指定。 If an attempt is made to modify the result of an assignment operator or to access it after the next sequence point, the behavior is undefined.如果尝试修改赋值运算符的结果或在下一个序列点之后访问它,则行为未定义。

Basically, in C11/C++11, they messed up when they removed this text.基本上,在 C11/C++11 中,当他们删除此文本时,他们搞砸了。

This is further information to the other answers and I'm posting it as the code below is often asked about as well .这是其他答案的进一步信息,我将其发布,因为下面的代码也经常被问到

The explanation in the other answers is correct and also applies to the following code which is now well-defined (and does not change the stored value of i ):其他答案中的解释是正确的,也适用于现在定义良好的以下代码(并且不会更改i的存储值):

i = i++;

The + 1 is a red herring and it's not really clear why the Standard used it in their examples, although I do recall people arguing on mailing lists prior to C++11 that maybe the + 1 made a difference due to forcing early lvalue conversion on the right-hand side. + 1是一个红鲱鱼,并不清楚为什么标准在他们的示例中使用它,尽管我确实记得有人在 C++11 之前的邮件列表中争论说, + 1可能由于强制早期左值转换而有所作为在右手侧。 Certainly none of that applies in C++17 (and probably never applied in any version of C++).当然,这些都不适用于 C++17(并且可能从未应用于任何版本的 C++)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM