Q我应该如何理解本节中的复杂表达式,并避免编写未定义表达式?什么是“顺序点”?
A顺序点是时间上的一个点,此时所有已产生的副作用都已完成并且确定是完整的。C 标准中列出的顺序点包括:
标准规定:
在上一顺序点和下一顺序点之间,一个对象最多只能被表达式的求值修改一次。此外,之前的值只能用于确定要存储的值。
这两句相当晦涩的话说明了几点。首先,它们谈论的是受“上一顺序点和下一顺序点”限制的操作;这些操作通常对应于完整表达式。(在表达式语句中,“下一顺序点”通常是终止的分号,而“上一顺序点”是上一条语句的末尾。表达式还可能包含中间顺序点,如上所述。)
第一句话排除了以下两个示例:
i++ * i++和
i = i++来自问题 3.2 和 3.3——在这两种情况下,i在表达式中,即在顺序点之间,其值被修改了两次。(如果我们编写一个包含内部顺序点的类似表达式,例如
i++ && i++它将是定义良好的,尽管可能值得怀疑。)
第二句话可能很难理解。事实证明,它禁止了如下代码:
a[i] = i++来自问题 3.1。(实际上,我们一直在讨论的其他表达式也违反了第二句话。)要理解原因,让我们先仔细看看标准试图允许和禁止的内容。
显然,像这样的表达式:
a = b和
c = d + e读取一些值并用它们来写入其他值,是定义良好的且合法的。显然,[脚注] 像这样的表达式:
i = i++修改同一个值两次是应该被禁止的(或者在任何情况下,不必被定义为良好),即我们不必弄清楚如何描述它们的作用,编译器也不必支持它们。这类表达式被第一句话禁止。
同样清楚的是[脚注],我们希望禁止像这样的表达式:
a[i] = i++修改i 并且在其过程中使用它,但又不禁止像这样的表达式:
i = i + 1使用并修改i但仅在相对容易确保最终的值(存入i,在本例中)不会干扰早期访问的情况下,在之后修改它。
这就是第二句话所说的:如果在完整表达式中写入了一个对象,那么在同一表达式中对它的任何访问都必须直接涉及要写入的值的计算。此规则有效地将合法表达式限制在那些访问明显先于修改的表达式中。例如,老式的i = i + 1是允许的,因为对i的访问用于确定i的最终值。示例
a[i] = i++被禁止,因为对i的访问之一(在a[i]中)与最终存储在i(发生在i++中)中的值无关,因此没有好的方法来定义——无论是为了我们的理解还是为了编译器——访问应该在增量值存储之前还是之后发生。由于没有好的定义方法,标准宣布它是未定义的,并且便携式程序根本不能使用此类构造。
参考文献:ISO Sec. 5.1.2.3, Sec. 6.3, Sec. 6.6, Annex C
Rationale Sec. 2.1.2.3
H&S Sec. 7.12.1 pp. 228-9