在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 上看到的一个例子。

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

++ 表达式的值计算在修改操作数对象之前进行排序。

这意味着,这样的例子会导致 UB

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

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

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

表达式 E1[E2] 与(根据定义)相同,*((E1)+(E2)),...,表达式 E1 在表达式 E2 之前排序

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

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

逻辑或表达式赋值运算符初始化子句

和expr.post#1

logical-or-expression这里是一个后缀表达式。那是,

postfix-expression [ arr ] = 5;

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

后缀表达式从左到右分组

所以,让下标操作有形式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++ 中也很好。

我有什么误解吗?

不。

代码是不是UB?

不。

如果不是UB,结果是什么?

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 代码的),因为在from上的arr[0]++[arr]++两个副作用是彼此未排序的。arr[0]++

  • In your second to last example, for `2[arr]++[arr] = 5;`, the side effect from `2[arr]++` is sequenced before the value computation of the second `arr`, which is sequenced before the assignment, so I don't see the UB there. Similarly for the last one. In other words, all side effects from the expressions on the left are sequenced before the value computation of the `arr` in the last `[arr]`, the last subscripting itself has no side effects, and its value computation is sequenced before the assignment, so it's that last `[arr]` that avoids UB from the final assignment in all cases.
  • Yes, in an expression of the form `e[f] = g`, `e` is sequenced before `f`, which means that the side effects of `e` are sequenced before the value computation of `f`, which is sequenced before the value computation of `e[f]`, which is sequenced before the side effect of `e[f] = g`.
  • @KamilCuk The side effects of `e[f]` (if any), yes, unsequenced with the assignment indeed, but the side effects of `e` are transitively sequenced before the assignment, because they are sequenced before the value computation of `f`.

以上是在UB中执行多个后缀表达式(下标)评估结果的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>