【问题标题】:Do the multiple postfix-expression(subscripting) evaluations result in UB多个后缀表达式(下标)评估是否会导致 UB
【发布时间】:2021-03-12 08:35:15
【问题描述】:
#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

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

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

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

因为,表达式arr[0]++(*(arr[0]++) + arr))++造成的副作用是无序的,应用到同一个内存位置。

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

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

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

为简化#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' 在@ 之前排序987654344@,因此由E1' 引起的副作用在E2' 的值计算之前排序。
换句话说,后缀++表达式引起的每个副作用都必须在该表达式与随后的[arr]结合之前进行评估。

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

【问题讨论】:

  • 不错的一个。我花了一点时间来处理那个表情。
  • @HolyBlackCat 坦率地说,这样的表达太复杂了,很难分解。
  • 好问题。但是人们不应该编写这样的代码。
  • 如何建造火车很有趣0[arr][arr][arr][arr]
  • @KamilCuk - 在 at 的海盗火车。啊!

标签: c++ c++17 language-lawyer


【解决方案1】:

我相信你的理解很好,从 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 代码的),因为在 arr[0]++[arr]++ 中,++arr[0] 的两个副作用彼此没有顺序。

【讨论】:

  • 在倒数第二个例子中,对于2[arr]++[arr] = 5;2[arr]++ 的副作用在第二个arr 的值计算之前排序,在赋值之前排序,所以我不那里看不到UB。最后一个也是如此。换句话说,左边表达式的所有副作用都排在最后一个arr的值计算之前[arr],最后一个下标本身没有副作用,它的值计算在赋值之前排序,所以最后一个[arr] 在所有情况下都避免了最终分配中的UB。
  • 对于倒数第二个示例,我认为那不是 UB。因为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.表示表达式2[arr]++[arr] 的值计算是赋值前的顺序,而The expression E1 is sequenced before the expression E2又是赋值前的顺序。
  • [expr.ass]/1:在所有情况下,赋值顺序在左右操作数的值计算之后,赋值表达式的值计算之前。 i> [expr.sub]/1: 表达式 E1 在表达式 E2 之前排序 [intro.execution]/15: 如果表达式 X 被称为在表达式 Y 之前排序与表达式 X 关联的每个值计算和每个副作用在每个 值计算和与表达式 Y 关联的每个副作用之前排序
  • 是的,在e[f] = g形式的表达式中,e排在f之前,这意味着e的副作用排在f的值计算之前,在e[f]的值计算之前排序,在e[f] = g的副作用之前排序。
  • @KamilCuk e[f] 的副作用(如果有的话),是的,确实与赋值无关,但 e 的副作用在赋值之前被传递排序,因为它们在之前排序f的值计算。
【解决方案2】:

multiply postfix incremented 表达式本身不是 UB,因为标准要求:

++表达式的值计算顺序在前面 操作数对象的修改。

因此,在每个x++[arr] 中,后缀递增在值计算之后被延迟,并将结束于arr[0][arr][arr][arr][arr][arr][arr] 并最终(因为arr[0] 是初始0)到arr[0]

然后,您将在此处对相同的 arr[0] 元素应用许多增量,并且正如 KalilCuk 所证明的那样,至少在 C++17 中,仅在该部分一切都会好起来的.由于arr[0]0,所有这些后增量都将应用于arr[0],即使在C++17 之前,在该特定用户案例中仍然没有UB。

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

所以你正在调用与更简单的相同的 UB:

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

可能不是你所期望的,但仍然是 UB...

【讨论】:

  • 也许我应该添加标签c++17
  • postfix incremented expression 本身的求值顺序并不能保证我的问题arr[0]++[arr]...[arr]=5 中的表达式是明确定义的,正如我在这个例子中提到的int arr[2] = {0}; (*(arr[0]++ + arr))++,副作用是无序的。跨度>
  • 另外,i++ = 1; 是非良构的,因为i++ 是一个prvalue。
  • 原程序的执行结果在 MSVC 和 gcc/clang 中是不同的。这可能是 UB 的另一个原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-13
  • 1970-01-01
  • 2019-01-09
  • 2016-08-16
  • 1970-01-01
  • 2014-02-26
  • 1970-01-01
相关资源
最近更新 更多