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

Behaviour of arr[i] = i++ and i = i + 1 statements in C and C++

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

因为这最初是用 and and not any specific version(s), the below answer is a generic answer to the problem. However, please note for , C++17 onwards, the behaviour has changed. Please see this [=23= 标记的]


为声明

arr[i] = i++;

i 的值 在两个操作数 RHS(右侧)和 LHS(左侧)中使用 ,并且在其中一种情况,该值正在被修改(作为 post ++ 的副作用),其中没有 sequence point in between to determine which value of i should be considered. You can also check this canonical answer 的更多信息。

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

  • 读取i
  • 的值
  • 增加 1
  • 将其存储回 i

按照明确的顺序。因此,没有问题。

arr[i] = i++;

暗示

  • 右边的表达式在赋值前求值
  • 下标运算符在赋值前求值

但在右手表达式求值和下标运算符求值的顺序方面存在歧义,编译器可以自由地将其视为

auto & val{arr[i]};
i++;
auto const rval{i};
val = rval;

i++;
auto & val{arr[i]};
auto const rval{i};
val = rval;

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

i++;
auto const rval{i};
auto & val{arr[i]};
val = rval;

这可能会产生不可预知的结果,而

i = i + 1;

没有任何歧义,右边的表达式在赋值前求值:

auto const rval{i + 1};
auto & val{i};
val = rval;

或(与上述结果相同)

auto & val{i};
auto const rval{i + 1};
val = rval;

对于 C99,我们有:

6.5 Expressions

  1. Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.

arr[i] = i++中,i的值只修改了一次。但是arr[i]也是从i读取的,这个值并没有用来确定i的新值。这就是它具有未定义行为的原因。

另一方面,在i = i + 1中我们读取i以计算i + 1,它被用作i的新值。所以这个表达式没问题。

在你的例子中,a [i] = i++,如果 i = 3,你认为先计算 a [i] 还是 i++?在一种情况下,值 3 将存储在 [3] 中,在另一种情况下,它将存储在 [4] 中。很明显,我们这里有问题。没有理智的人敢写这样的代码,除非他们能保证这里究竟会发生什么。 (Java 给出了保证)。

您认为 i = i + 1 可能有什么问题?该语言必须首先读取 i 以计算 i+1,然后存储该结果。这里没有什么可能是错的。与 [i] = i+1 相同。与 i++ 不同,计算 i+1 不会改变 i。所以如果 i = 3,数字 4 必须存储在 [3] 中。

不同的语言有不同的规则来解决 [i] = i++ 的问题。 Java 定义 会发生什么:表达式从左到右求值,包括它们的副作用。 C 将其定义为未定义的行为。 C++ 不会使其成为 undefined 行为,只是未指定。它说先计算 a[i] 或 i++,然后计算另一个,但没有说明是哪一个。因此,与 anything 可能发生的 C 不同,C++ 定义了两种情况中只有一种可能发生。显然,在您的代码中,这是一件无法接受的事情。

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

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.

也就是说,我们做i++ 然后我们做arr[i]然后我们执行赋值。现在明确定义的顺序是:

auto src = i++;
auto& dst = arr[i];
dst = src;