我已经研究了序列点(C99)和排序前/未排序(C17)以及SO中关于这个主题及其与未定义行为的关系的其他帖子。
我认为在C99中,i=i++;
和i=++i;
都导致了未定义的行为,但根据C17(甚至C23),我们有:
如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用同一标量对象的值进行的值计算未排序,则行为未定义。如果表达式的子表达式有多个允许的排序,则如果任何排序中出现此类未排序的副作用,则行为未定义。
因此,这:
i=i++;
字符串
导致未定义的行为,因为我们在这里要副作用后缀++操作符和赋值,并且根据这篇文章Why are these constructs using pre and post-increment undefined behavior?中hacks的答案,我们知道这两个副作用相对于彼此都是无序的,所以这导致了未定义的行为,但是这个呢:
i=++i;
型
至少就我在这里所知,i
的递增值应该在完整表达式中使用,所以我们再次有两个副作用,但现在预递增运算符的副作用结果必须在赋值运算符的副作用结果之前排序,对吗?
现在我的理论,根据C11/C17/C23,“**i=++i;**是未定义的行为,是因为预增量运算符的副作用结果相对于LHS中“i”的值计算是无序的,对吗?
2条答案
按热度按时间rn0zuynd1#
从https://port70.net/~nsz/c/c11/n1570.html#6.5.3.1p2或https://port70.net/~nsz/c/c99/n1256.html#6.5.3.1p2:
前缀运算符的操作数的值递增。结果是操作数递增后的新值。表达式E等价于(E+=1)。[...]
从https://port70.net/~nsz/c/c11/n1570.html#6.5.16.2p3或https://port70.net/~nsz/c/c99/n1256.html#6.5.16.2p3:
形式为E1 op = E2的复合赋值等价于简单赋值表达式E1 = E1 op(E2),除了左值E1只计算一次[...]
所以我们有
i = (i = i + 1);
,其中两个i
被计算一次。从https://port70.net/~nsz/c/c11/n1570.html#6.5.16p3或https://port70.net/~nsz/c/c99/n1256.html#6.5.16p3:
赋值运算符在左操作数指定的对象中存储一个值。赋值表达式在赋值后具有左操作数的值,(111)但不是左值。赋值表达式的类型是左操作数在左值转换后的类型。更新左操作数的存储值的副作用是在左操作数和右操作数的值计算之后排序。操作数的求值是无序的。
现在我们启动引擎,让我们把它们标记为
i₁ = (i₂ = i₃ + 1);
,只是为了知道我指的是哪个i
。因此,更新
i₂
是在计算i₃ + 1
之后进行的。更新
i₁
的顺序是在 * 计算 *i₂ = i₃ + 1
之后。但是,我...更新
i₁
和更新i₂
是 * 无序的 *。我们不知道更新i₂
的副作用相对于表达式i₂ = i₃ + 1
的值计算会在“什么时候”发生。i₂
可以在之后立即更新,也可以在 * 下一个序列点 * 更新,即;
。但是
i₂ = i₃ + 1
之后没有序列点。i₁
的更新是在 * 计算 *i₂ = i₃ + 1
之后,但是不与更新i₂
排序。在更新i₁
的时候,更新i₂
的副作用仍然是“挂起”,“要做”。因为更新i₁
和i₂
是不排序的,这就是为什么它是未定义的行为。atmip9wb2#
至少就我所知,i的递增值应该在完整表达式中使用,所以我们又有两个副作用,但现在预递增运算符的副作用结果必须在赋值运算符的副作用结果之前排序,对吗?
不,
=
操作符不引入序列点。++i
* 将 * 求值为i
的当前值加1,但更新i
的 * 副作用 * 不必在求值时或赋值前立即应用。以下是完全有效的求值顺序:字符串
i = ++i
和i = i++
一样是未定义的。前后递增(或递减)没有区别。