在C和C++中arr [i] = i ++和i = i + 1语句的行为

Jay*_*esh 18 c c++ undefined-behavior sequence-points c++14

在C和C++语言中,该arr[i] = i++;语句调用未定义的行为.为什么语句i = i + 1;不会调用未定义的行为?

Sou*_*osh 36

由于这最初是用和标记的,而不是任何特定的版本,因此下面的答案是问题的通用答案.但请注意,对于,C++17从那时起,行为就发生了变化.请参阅Barry的回答以了解更多信息.


对于声明

arr[i] = i++;
Run Code Online (Sandbox Code Playgroud)

的值i被用于在两个操作数,RHS(右手侧)和LHS(左手侧),并且在其中一种情况下,该值被修改(作为后的副作用++),其中有两者之间没有序列点来确定i应该考虑哪个值.您还可以查看此规范答案以获取更多信息.

在另一方面,对i = i + 1,价值i是使用只在RHS,计算结果存储在LHS,换句话说,没有歧义.我们可以写相同的语句i++,这

  • 读取的值 i
  • 增加它 1
  • 把它存回来 i

在明确定义的序列中.因此,没有问题.

  • 几年来,C++放弃了"排序点",转而采用基于"后序排序"的更复杂的描述,例如`i = ++ i;`合法代码. (7认同)
  • `i = i + 1`不等同于`i ++`.它实际上相当于`++ i`.比较`j = i = i + 1`与`j = ++ i`和`j = i ++`的效果,看看区别(记住赋值运算符从右到左关联,所以`j = i = i + 1`相当于`j =(i = i + 1)`). (5认同)
  • 很好地回答了这个角落的非重复变体. (4认同)
  • @SouravGhosh - 我的问题是你写了第二段,表明`i = i + 1`和`i ++'是等价的,当它们不是时.提出这个建议对于回答这个问题是没有必要的,也是误导性的. (4认同)

Bar*_*rry 12

请注意,这将在C++ 17中发生变化.在C++ 17中,arr[i] = i++不会调用未定义的行为.这是由于[expr.ass]中的以下更改:

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

也就是说,我们不i++ 那么我们就arr[i] 接着我们进行分配.现在明确定义的顺序是:

auto src = i++;
auto& dst = arr[i];
dst = src;
Run Code Online (Sandbox Code Playgroud)

  • @KevinZ为什么还好?在这种情况下,有什么额外的未定义行为的好处是什么? (3认同)
  • 虽然这现在有效,但它仍然是一个非常脆弱的表达.幸运的是,如果您正在使用GCC,则可以使用`-fno-strong-eval-order`来保持此垃圾表达式未定义. (2认同)
  • 是的,写这样的表达式很糟糕,纯粹是因为它看起来多么模糊和虚假 - 但在代码中留下一个定时炸弹并迫使你的编译器避免遵守新标准更糟糕。编写好的代码应该通过推理来鼓励,而不是被 UB 气馁......_你希望,如果 UB 在为时已晚之前可检测到_。事实上,坦率地说,建议人们依赖 UB 的东西,而且显然如此,是非常愚蠢的。编写此代码的人需要从中得到解释;他们不太可能与 UBsan 或其他任何东西一起运行 (2认同)

mel*_*ene 9

对于C99,我们有:

6.5表达式

  1. 在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算来修改一次.此外,先前的值应该只读以确定要存储的值.

在arr[i] = i++,值i只修改一次.但arr[i]也读取i,并且此值不用于确定新值i.这就是为什么它有未定义的行为.

另一方面,在i = i + 1我们读取i以便计算时i + 1,它被用作新的值i.因此这个表达很好.


use*_*670 8

arr[i] = i++;
Run Code Online (Sandbox Code Playgroud)

暗示

  • 在分配之前评估右手表达
  • 在赋值之前评估下标运算符

但是包含右手表达式评估和下标操作员评估顺序的含糊不清,编译器可以自由地将其视为

auto & val{arr[i]};
i++;
auto const rval{i};
val = rval;
Run Code Online (Sandbox Code Playgroud)

或者作为

i++;
auto & val{arr[i]};
auto const rval{i};
val = rval;
Run Code Online (Sandbox Code Playgroud)

或者(与上述相同的结果)

i++;
auto const rval{i};
auto & val{arr[i]};
val = rval;
Run Code Online (Sandbox Code Playgroud)

这可能会产生不可预测的结果

i = i + 1;
Run Code Online (Sandbox Code Playgroud)

dos没有任何歧义,在赋值前评估右手表达式:

auto const rval{i + 1};
auto & val{i};
val = rval;
Run Code Online (Sandbox Code Playgroud)

或(与上述相同)

auto & val{i};
auto const rval{i + 1};
val = rval;
Run Code Online (Sandbox Code Playgroud)