第一句话:
在前一个序列点和下一个序列点之间,一个对象应该有它的
表达式的求值最多修改一次存储的值。
够清楚了。该语言不会对子表达式施加求值顺序,除非它们之间存在序列点,并且它不需要 some 未指定的求值顺序,它表示修改对象两次会产生未定义的行为。这允许积极优化,同时仍然可以编写遵循规则的代码。
下一句:
此外,先读值应只读以确定要存储的值
乍一看(和第二眼)确实不直观;为什么读取值的目的会影响表达式是否具有已定义的行为?
但它反映的是,如果子表达式 B 依赖于子表达式 A 的结果,则必须先计算 A,然后才能计算 B。 C90 和 C99 标准没有明确说明这一点。
在脚注的一个例子中给出的一个更明显的违反该句子的例子是:
a[i++] = i; /* undefined behavior */
假设a是一个声明的数组对象,i是一个声明的整数对象(没有指针或宏诡计),没有对象被修改超过一次,所以它不违反第一句。但是 LHS 上i++ 的评估决定了要修改哪个对象,RHS 上i 的评估决定了要存储在该对象中的值——以及 RHS 上读取操作的相对顺序并且没有定义 LHS 上的写操作。同样,该语言可能要求以某种未指定的顺序对子表达式进行求值,但相反,它使整个行为未定义,以允许更积极的优化。
在你的例子中:
int i = 0, *a = &i;
a[i] = i; /* undefined behavior (I think) */
读取i 的前一个值以确定要存储的值和以确定它将被存储在哪个对象中。因为a[i] 指的是i(但是只是因为i==0),修改i的值会改变左值a[i]所指的对象。在这种情况下,i 中存储的值与已存储在那里的值 (0) 相同,但标准不会对碰巧存储相同值的存储进行例外处理。我相信这种行为是不确定的。 (当然,标准中的示例并不打算涵盖这种情况;它隐含地假设 a 是一个声明的数组对象,与 i 无关。)
至于标准所说的允许的例子:
int a[10], i = 0; /* implicit, not stated in standard */
a[i] = i;
一个可以解释标准说它是未定义的。但我认为第二句话,指的是“先验值”,仅适用于被表达式修改的对象的值。 i 永远不会被表达式修改,因此不存在冲突。 i 的值既用于确定要通过赋值修改的对象,也用于确定要存储在那里的值,但这没关系,因为i 的值本身永远不会改变。 i 的值不是“先验值”,它只是值。
C11 标准为这种表达式评估提供了一个新模型——或者更确切地说,它用不同的词来表达相同的模型。它不是“序列点”,而是谈论在彼此之前或之后排序的副作用,或者相对于彼此不排序的副作用。它明确表示如果子表达式 B 依赖于子表达式 A 的结果,则必须在计算 B 之前计算 A。
在the N1570 draft,第 6.5 节说:
1 表达式是一系列运算符和操作数
指定一个值的计算,或指定一个对象
或功能,或产生副作用,或执行
它们的组合。操作数的值计算
运算符的值在计算值之前排序
运算符的结果。
2 如果标量对象的副作用相对于
对同一标量对象的不同副作用或
使用相同标量对象的值计算值,
行为未定义。如果有多个允许的排序
在表达式的子表达式中,行为未定义
如果在任何排序中出现这种未排序的副作用。
3 运算符和操作数的分组由语法指示。
除非稍后指定,副作用和价值计算
的子表达式是未排序的。