序列点和求值顺序

4
我正在阅读K&R,我遇到了一个关于表达式评估中不确定行为的例子,如a[i]=i++; C99规范在$6.5.2中说:

在前一个和下一个序列点之间,对象通过表达式的评估最多只能修改其存储值一次。此外,先前的值只能被读取以确定要存储的值。

上面来自K&R的例子在第一条语句上是正确的。请解释第二条语句为什么会失败。
标准是否对涉及序列点时子表达式的评估顺序有任何规定?例如:a[i++] || b[i++]。我知道这些是从左到右评估的,但这是否可以从上述语句中推导出来,或者是否在标准中明确说明了?

1
可能是未定义行为和序列点的重复问题。 - Lundin
据我所知,就序列点和求值顺序而言,C语言和C++并没有区别。 - Lundin
@Lundin:那么,这可能是一个好答案的一部分。不过,这个问题并不是重复的。 - undur_gongor
@undur_gongor 哦,拜托,每周至少有五个 i+++++i 的问题。至于为什么赋值运算符不是序列点的特定情况,其他人也链接了一篇文章。 - Lundin
https://dev59.com/L3NA5IYBdhLWcg3wdtv5 可能是这个重复群中关注C语言的“最大”的成员 - Steve Jessop
2个回答

4
标准是否针对序列点中子表达式的求值顺序做出了规定?
在条件运算符&&和||的情况下,求值顺序是明确定义的,这也是短路工作的原因。
C99标准明确规定了这一点。
参考文献:c99标准
附录J:J.1未指定的行为
以下内容是未指定的:
......
除函数调用(), &&, ||, ?: 和逗号运算符(6.5)指定的情况外,子表达式的求值顺序和副作用发生的顺序。
......
进一步地,在逻辑或运算符中,保证从左到右进行求值;在第一个操作数求值后有一个序列点。如果第一个操作数不等于0,则不会计算第二个操作数。
同样,在逻辑与运算符中:
与位运算&运算符不同,&&运算符保证从左到右进行求值;如果计算第二个操作数,则在第一个和第二个操作数之间有一个序列点。如果第一个操作数等于0,则不会计算第二个操作数。

1
谢谢,但您能否请提供一些关于我问的第一个问题a[i]=i++的解释? - Bazooka
@Parminder 在 C 语言中,赋值运算符不是一个序列点。注意:C++11 引入了顺序前/后的概念,a[i]=i++ 在 C++11 中是定义良好的。另请参见 https://dev59.com/CG855IYBdhLWcg3wZzff - Suma
太棒了!这个回答帮助我理解 a || b++ && cb++ 是否总是被计算。实际上不是,但是 && 运算符的优先级更高,这会引起很多人争论。 - Kevin P. Rice

0
关于第一个问题:
该句适用于被表达式更改的对象,即 i(和 a[i])。因此,必须仅使用 i 的先前值来确定 i 的“新”值。
但是,表达式还需“使用”它来确定要写入的数组元素。
背景是,否则就不清楚 i 是指增量前还是增量后的值。

谢谢,但我还有另一个关于这个问题的疑问。像 ++i==i 这样的表达式乍一看似乎是模棱两可的。但它并没有试图“改变”除 i 以外的任何东西。那么这也是未定义行为吗? - Bazooka
我不想改变任何左值。我的意思是,在从右到左求值时,我只更改了一次“i”,当我第二次访问“i”时,我这样做是为了更改“i”的值本身。这应该符合第二个语句的要求。那么问题在哪里?这是一个具有确定行为的合法表达式吗? - Bazooka
这里没有重新赋值,只有增量操作。这是 == 而不是 =, 以防你错过了。 - Bazooka
抱歉,我可能眼瞎了。但是,你正在使用对象i的值不仅用于计算i的新值,还用于比较。因此,你正在违反与你问题中相同的约束条件。 - undur_gongor
1
我现在明白了。这个表达式是未定义的,因为“在两个序列点之间被修改的对象的任何访问都应该只更改该对象而不应该做其他操作。”在这个例子中,我们用它来进行比较,违反了第二个“规则”。但它仍然适用于第一个语句。干杯!! - Bazooka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接