【发布时间】: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
逻辑或表达式赋值运算符初始化子句
这里的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