在 UB 中执行多个后缀表达式(下标)评估结果

jac*_*k X 17 c++ language-lawyer c++17

#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;
   }
}
Run Code Online (Sandbox Code Playgroud)

考虑上面的代码,这个评估#1是否会导致 UB?这是我在twitter 上看到的一个例子。

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

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

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

int arr[2] = {0};
(*(arr[0]++ + arr))++
Run Code Online (Sandbox Code Playgroud)

因为,表达式引起的副作用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;
Run Code Online (Sandbox Code Playgroud)

后缀表达式的形式为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...
Run Code Online (Sandbox Code Playgroud)

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

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

Kam*_*Cuk 8

我相信您的理解很好,并且代码在 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
Run Code Online (Sandbox Code Playgroud)

我看到输出将是:

0 : 2
1 : 3
2 : 4
3 : 5
4 : 4
5 : 3
6 : 2
Run Code Online (Sandbox Code Playgroud)

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

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

  • 在倒数第二个示例中,对于“2[arr]++[arr] = 5;”,“2[arr]++”的副作用在第二个“arr”的值计算之前进行排序,即在作业之前排序,所以我在那里看不到 UB。最后一个也是如此。换句话说,左边表达式的所有副作用都排序在最后一个“[arr]”中的“arr”的值计算之前,最后一个下标本身没有副作用,并且它的值计算排序在分配,因此最后一个“[arr]”在所有情况下都避免了最终分配中的 UB。 (3认同)
  • 是的,在“e[f] = g”形式的表达式中,“e”在“f”之前排序,这意味着“e”的副作用在“f”的值计算之前排序,即在`e[f]`的值计算之前排序,在`e[f] = g`的副作用之前排序。 (2认同)
  • @KamilCuk `e[f]` 的副作用(如果有),是的,确实与赋值无关,但是 `e` 的副作用在赋值之前被传递排序,因为它们在 ` 的值计算之前排序。 f`。 (2认同)