32

在 C++ 中,以下是否具有未定义的行为:

int i = 0;
(i+=10)+=10;

在对我对C 和 C++ 中 += 的结果是什么的回答的评论中对此进行了一些辩论?这里的微妙之处在于默认响应似乎是“是”,而正确的答案似乎是“它取决于 C++ 标准的版本”。

如果它确实取决于标准的版本,请说明它在哪里是 UB,在哪里不是。

4

3 回答 3

34

tl;dr:在 C++98 和 C++11中执行的修改和读取的顺序(i+=10)+=10在 C++98 和 C++11 中都有很好的定义,但是在 C++98 中,这不足以定义行为。

在 C++98 中,对同一个对象进行多次修改而没有插入序列点会导致未定义的行为,即使这些修改的顺序已明确指定。该表达式不包含任何序列点,因此它由两个修改组成的事实足以使其行为未定义。

C++11 没有序列点,只要求对象的修改相对于彼此进行排序,并且读取同一对象以产生定义的行为。

因此,该行为在 C++98 中未定义,但在 C++11 中定义良好。


C++98

C++98 子句 [expr] 5 p4

除非另有说明,单个运算符的操作数和单个表达式的子表达式的求值顺序以及副作用发生的顺序是未指定的。

C++98 子句 [expr.ass] 5.17 p1

赋值操作的结果是赋值后存储在左操作数中的值;结果是一个左值

所以我相信顺序是指定的,但是我认为仅凭这一点不足以在表达式中间创建一个序列点。继续引用 [expr] 5 p4:

在前一个和下一个序列点之间,一个标量对象的存储值最多只能通过表达式的评估修改一次。

因此,即使指定了顺序,在我看来这对于 C++98 中定义的行为来说还是不够的。


C++11

C++11 取消了序列点,以便更清晰地了解sequence-beforesequenced-after。C++98 中的语言被替换为

C++11 [intro.execution] 1.9 p15

除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值是无序的。[...]

如果标量对象上的副作用相对于同一标量对象上的另一个副作用或使用同一标量对象的值的值计算是未排序的,则行为未定义。

C++11 [expr.ass] 5.17 p1

在所有情况下,赋值都在左右操作数的值计算之后和赋值表达式的值计算之前进行排序。

因此,虽然有序不足以实现 C++98 中定义的行为,但 C++11 改变了要求,使得有序(即有序)就足够了。

(在我看来,'sequence before' 和 'sequenced after' 提供的额外灵活性导致了更清晰、一致和明确的语言。)


在我看来,任何 C++98 实现实际上都不太可能在操作序列被明确指定时做出任何令人惊讶的事情,即使这不足以产生技术上定义良好的行为。例如,Clang 在 C++98 模式下生成的这个表达式的内部表示具有明确定义的行为并执行预期的操作。

于 2012-05-18T15:55:48.673 回答
20

在 C++11 中,表达式定义明确,将导致i == 20.

来自[expr.ass]/1

在所有情况下,赋值都在左右操作数的值计算之后和赋值表达式的值计算之前进行排序。

这意味着赋值i+=1在 的左侧的值计算之前(i+=10)+=10排序,而在最终赋值之前依次排序i


在 C++03 中,表达式具有未定义的行为,因为它导致i被修改两次而没有中间序列点。

于 2012-05-18T15:20:25.003 回答
13

也许。这取决于 C++ 版本。

在 C++03 中,这是一个明显的 UB,分配之间没有干预序列点。

正如 Mankarse 解释的那样,在 C++11 中,它不再是未定义的——括号中的复合赋值在外部赋值之前排序,所以没关系。

于 2012-05-18T15:28:03.393 回答