Q: 3.8 如何理解复杂表达式并避免未定义行为?什么是顺序点?


A: 一个顺序点是尘埃落定,和到目前为止所有可见副作用保证完成时的一刻。C标准中列出的顺序点是:

标准上声明

在前一个和后一个顺序点之间,通过表达式求值,一个对象使它的值最多只能改变一次。而且,先前的值仅可被访问用于确定将被存储的值。

这两句相当含糊的话说了几个事情。第一,它们谈论在“前一个和后一个顺序点”之间的运算;这些运算通常对应完整表达式。(在一个表达式语句中,“下一个顺序点”通常是在结尾的分号,“前一个顺序点”在前一个语句尾。一个表达式也可以包含中间的顺序点,像在上面列出的。)

第一句话排除了问答3.2和3.3中这两个例子

  i++ * i++

  i = i++

——在两种情形中,在表达式中,i修改了它的值两次,即是在顺序点之间。(如果我们写一个类似的有一个内部顺序点的表达式,比如

  i++ && i++

它将是良好定义的,如果不确定其有用性。)

第二句话很难理解。它看起来不允许问答3.1中这样的代码

  a[i] = i++

(事实上,我们正要讨论的其它表达式也违反第二句话。) 为了明白为什么,让我们先更仔细看看标准尝试允许和禁止什么。

显然,这样的表达式

  a = a

  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++中发生),所以没有一种好的方法来定义——对我们的理解或编译器——是否这个访问发生在增量值被存储之前或之后。既然不能很好定义,标准声称它是未定义的,并且可移植程序简单地不可以使用这类构造。

同时参见问答3.9和3.11。

参考: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


(This Chinese translation isn't confirmed by the author, and it isn't for profits.)

Translator : jhlicc@gmai1.c0m
Origin : http://www.c-faq.com/expr/seqpoints.html