在 C99 6.5 中说:
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。此外,应仅读取先验值以确定要存储的值
“此外,应仅读取先验值以确定要存储的值”是什么意思?在 C99 中,为什么a[i++] = 1
是未定义的行为?
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。此外,应仅读取先验值以确定要存储的值
“此外,应仅读取先验值以确定要存储的值”是什么意思?在 C99 中,为什么a[i++] = 1
是未定义的行为?
a[i++] = 1
已定义(除非除了副作用的顺序之外还有其他未定义的原因:越界访问或未初始化i
)。
您的意思是a[i++] = i
,这是未定义的行为,因为它i
在与 相同的序列点之间读取i++
,这会改变它。
“此外,应仅读取先验值以确定要存储的值”部分表示i = i + 1;
允许,尽管它读取i
并修改了i
.
另一方面,a[i] = (i=1);
这是不允许的,因为尽管i
只写入一次,但读取i
并不用于计算存储的值。
“应仅读取先验值以确定要存储的值”的措辞无疑是违反直觉的;为什么读取值的目的很重要?
这句话的重点是要求哪些结果取决于哪些操作。
我将从Pascal 的答案中窃取示例。
这个:
i = i + 1;
很好。i
在同一个表达式中读取和写入,没有中间的序列点,但是没关系,因为直到读取完成后才会发生写入。i + 1
在完全计算表达式及其子表达式之前,无法计算要存储的值i
。(并且i + 1
没有可能延迟到写入之后的副作用。)这种依赖性强加了严格的顺序:必须在写入开始之前完成读取。
另一方面,这是:
a[i] = (i=1);
具有未定义的行为。子表达式a[i]
读取的值i
,子表达式i=1
写入的值i
。但是写入存储的值i
不依赖于i
左侧读取的评估,因此没有定义读取和写入的顺序。“要存储的值”是1
; i
in的读取a[i]
并不能确定该值。
我怀疑这种混淆是 ISO C 标准的 2011 年修订版(以草案形式N1570 提供)重新措辞该部分的原因。该标准仍然有序列点的概念,但 6.5p2 现在说:
如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用相同标量对象的值的值计算是未排序的,则行为未定义。如果表达式的子表达式有多个允许的排序,则如果在任何排序中出现这种未排序的副作用,则行为未定义。
第 1 段明确说明了 C99 中仅隐含假设的内容:
运算符的操作数的值计算在运算符结果的值计算之前排序。
第 5.1.2.3 节第 2 段解释了排序前和排序后的关系。