多个后缀表达式(下标)评估是否导致 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
这里的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...
#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
这里的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...