序列点和副作用:C11的安静变化?

C99§6.5 表达式

(1)表达式是操作符和操作数的序列,其指定值的计算,或指定对象或函数,或者生成副作用,或执行其组合。

(2)在前一个和下一个序列点之间,一个对象的存储值最多只能通过表达式的计算来修改一次。 72)此外,先前的值应该是只读的,以确定要存储的值。 73)

用脚注

72)浮点状态标志不是对象,可以在表达式中多次设置。

73)此段落呈现未定义的语句表达式,如

i = ++i + 1; a[i++] = i; 

同时允许

  i = i + 1; a[i] = i; 

C11§6.5改为((1)的文本有附录):

(1)[…]运算符的操作数的值计算在运算符的结果的值计算之前被排序。

(2)如果对标量对象的副作用相对于对同一标量对象的不同副作用或使用相同标量对象的值进行的值计算未被排序,则行为未定义。 如果表达式的子表达式有多个允许的排序,则如果在任何排序中发生这种未测序的副作用,则行为是不确定的。 84)

其中C11中的脚注84与C99中的73相同。

我有点困惑……我把C11(2)看作是“[…](对同一个标量对象的不同副作用)或(使用相同标量对象的值进行值计算)[…]”甚至不允许foo = ++i (有副作用,我们根据更改的对象使用值)。 不过,我不是母语人士,所以如果能告诉我这句话应该如何“解析”会更好。 我理解C99,但我不太明白C11的措辞。

无论如何,实际问题:这是从C99到C11的变化,还是这些措辞相当? 如果是这样,为什么它会被改变? 如果没有,有人可以给出一个表达式的例子,这个表达式在C99中是UB但在C11中不是,反之亦然?

C11(以及C ++ 11)完全重写了排序的措辞,因为C11现在有线程,它必须解释访问相同数据的线程之间的顺序。 委员会的目的是在只有一个执行线程的情况下使事情向后兼容C99。

我们来看看C99版本:

  1. 在前一个和下一个序列点之间

  2. 一个东西

  3. 应该有

  4. 其存储值最多修改一次

  5. 通过表达式的评估。

与新文本相比

如果有副作用

不同的terminolgie为4,修改储值

标量对象

对先前措辞的限制2.新文本仅说明了标量对象

没有相对于任何一个

unsequenced是1中概念的概括,即两个语句由序列点分隔。 想象两个线程修改相同的数据而不使用锁或类似的东西。

对同一个标量对象产生不同的副作用

该对象只允许修改一次

或者使用相同标量对象的值进行值计算,

或者读取的值可能不会同时出现在修改中

行为未定义。

3.中的“应”是含蓄地说的。 如果没有履行,所有“应该”导致UB。

我有点困惑……我把C11(2)看作是“[…](对同一个标量对象的不同副作用)或(使用相同标量对象的值进行值计算)[…]”甚至不允许foo = ++i (有副作用,我们根据更改的对象使用值)。

如果您仔细阅读标准报价

如果对标量对象副作用相对于对同一标量对象的不同副作用或使用相同标量对象的值进行的值计算未进行排序,则行为未定义。 如果表达式的子表达式有多个允许的排序,则如果在任何排序中发生这种未测序的副作用,则行为是不确定的。 84)

然后你会发现你的措辞应该是:

如果对标量对象副作用相对于其中任何一个 (对同一标量对象的不同副作用)或(使用相同标量对象的值进行值计算)未进行排序

这意味着foo = ++i是一个已定义的语句。 确实存在对i的副作用(也在foo ),但对于对象i这里没有任何后果。

这是foo = ++i的解释,但不是这个问题的答案。


前缀增量是根据复合赋值定义的,见6.5.3 / 2

表达式++E相当于(E+=1)

对于一般的转让,有6.5.16 / 3的保证

在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序。 对操作数的评估是不确定的。

所以foo = ++i相当于foo = (i+=1) 。 内部i+=1要求在计算i+1之后对i+1的修改进行排序。 表达式(i+=1)的结果值在6.5.16 / 3中指定为:

赋值表达式在赋值后具有左操作数的值,但不是左值。

似乎这需要在修改i之后对i+=1的值计算进行排序,而在C ++ 11中,这甚至可以明确地保证[expr.ass] / 1

在所有情况下,在右和左操作数的值计算之后,以及在赋值表达式的值计算之前,对赋值进行排序。

(这对我来说更清楚,但我知道C ++远胜于C)

i+=1的值计算之前对i的修改进行了排序,因此我们没有UB在foo = ++i访问foo = ++i的值(作为foo = x左右操作数的值计算)在修改foo之前排序。

据我了解,

如果对标量对象的副作用相对于…使用相同标量对象的值进行值计算未被排序

这里不适用,因为(1)表明了这一点

在运算符的结果的值计算之前,对运算符的操作数的值计算进行排序。

换句话说,结果被定义为“稍后”,即它排序。