多个后缀表达式(下标)评估是否导致 UB

Do the multiple postfix-expression(subscripting) evaluations result in UB

#include <iostream>
int main(){
   int  arr[7] = {0,1,2,3,4,3,2};
   arr[0]++[arr]++[arr]++[arr]++[arr]++[arr]++[arr] = 5;  //#1
   for(auto i = 0;i<7;i++){
       std::cout<<i<<" : "<< arr[i]<<std::endl;
   }
}

考虑上面的代码,#1 处的计算是否会导致 UB?这是我在twitter.

看到的一个例子

根据后缀++的求值顺序:
expr.post.incr#1

The value computation of the ++ expression is sequenced before the modification of the operand object.

也就是说,这样的例子会导致 UB

int arr[2] = {0};
(*(arr[0]++ + arr))++

因为表达式 arr[0]++(*(arr[0]++) + arr))++ 引起的副作用是未排序的,并且应用于相同的内存位置。

但是,对于第一个例子,这是不同的。我的论点是:
expr.sub#1

The expression E1[E2] is identical (by definition) to *((E1)+(E2)),..., The expression E1 is sequenced before the expression E2.

这意味着,与 E1 相关的每个值计算和副作用都在与 E2 相关的每个值计算和副作用之前排序。

为了简化#1处的表达式,根据表达式的语法,这样的表达式应该符合: expr.ass

logical-or-expression assignment-operator initializer-clause

expr.post#1

这里的logical-or-expression是后缀表达式。即,

postfix-expression [ arr ] = 5;

其中后缀表达式的形式为 postfix-expression ++,而 postfix-expression 的形式为 postfix-expression[arr]。简单来说,赋值的左操作数由两种后缀表达式组成,它们交替组合。

Postfix expressions group left-to-right

所以,让下标运算的形式为E1[E2],后缀++表达式的形式为PE++,那么对于第一个例子,它会给出如下分解:

E1': arr[0]++ 
E2': arr
E1'[E2']: arr[0]++[arr]
PE'++ : E1'[E2']++

E1'': PE'++
E2'': arr
E1''[E2'']: PE'++[arr]
PE''++: E1''[E2''] ++

and so on...

这意味着,为了计算 PE'++E1'[E2'] 应该先于 PE'++ 计算,这与 *((E1')+E2') 相同,根据规则 E1'E2' 之前排序,因此由 E1' 引起的副作用在 E2'.
的值计算之前排序 换句话说,由 postfix++ 表达式引起的每个副作用必须在该表达式与随后的 [arr].

组合之前进行评估

所以,通过这种方式,我认为 #1 中的此类代码应该具有明确定义的行为而不是 UB。我有什么误解吗?代码是否为UB?如果不是 UB,代码给出的正确结果是什么?

我相信你的理解很好,代码从 C++17 开始在 C++ 中也很好。

Is there anything I misunderstand?

没有

Whether the code is UB or not?

没有

If it's not UB, what's the result?

arr[0]++[arr]++[arr]++[arr]++[arr]++[arr]++[arr] = 5;
Side effect: arr[0] := 0 + 1 = 1
       0[arr]++[arr]++[arr]++[arr]++[arr]++[arr] = 5;
Side effect: arr[0] := 1 + 1 = 2
              1[arr]++[arr]++[arr]++[arr]++[arr] = 5;
Side effect: arr[1] := 1 + 1 = 2
                     1[arr]++[arr]++[arr]++[arr] = 5;
Side effect: arr[1] := 2 + 1 = 3
                            2[arr]++[arr]++[arr] = 5;
Side effect: arr[2] := 2 + 1 = 3
                                   2[arr]++[arr] = 5;
Side effect: arr[2] := 3 + 1 = 4
                                          3[arr] = 5;
Side effect: arr[3] := 5

我看到输出是:

0 : 2
1 : 3
2 : 4
3 : 5
4 : 4
5 : 3
6 : 2

请注意,The expression E1 is sequenced before the expression E2 部分是在 C++17 中添加的。

该代码在 C++17 之前未定义,在 C 中未定义(推文是关于 C 代码的),因为在 arr[0]++[arr]++ 中,++arr[0] 的两种副作用彼此无序。

乘 postfix 增量表达式本身不是 UB,因为标准要求:

The value computation of the ++ expression is sequenced before the modification of the operand object.

所以在每个 x++[arr] 中,postfix 递增在值计算之后被推迟,并将结束到 arr[0][arr][arr][arr][arr][arr][arr] 和最终(因为 arr[0] 是初始值 0) 到 arr[0].

然后你将有一些增量应用到同一个 arr[0] 元素,正如 KalilCuk 所展示的,一切都会很好 单独在那个部分 至少在 C++17 中。由于 arr[0]0 所有这些 post 增量将应用于 arr[0],即使在 C++17 之前,在该特定用户情况下仍然没有 UB。

问题是赋值不是序列点。因此,编译器可以选择先应用赋值,然后递增结果(这将给出 11),或者先递增值(将变为 6),然后处理赋值,最终结果为 5.

因此您正在调用与更简单的相同的 UB:

i = 0;
i++ = 1;     // 1 or 2 ?

可能不是您预期的那个,但仍然是 UB...