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

时间:2014-01-11 19:04:13

标签: c c99 undefined-behavior c11 sequence-points

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中不是,反之亦然吗?

4 个答案:

答案 0 :(得分:4)

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

让我们来看看C99版本:

  1.   

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

  2.   

    一个对象

  3.   

    应该

  4.   

    其存储值最多修改一次

  5.   

    通过表达式的评估。

  6. 与新文本相比

      

    如果有副作用

    4的不同terminolgie,修改存储值

      

    标量对象

    对先前措辞的限制2.新文本仅说明 关于标量物体的一些东西

      

    相对于任何一个

    都没有排序

    unsequenced是概念的概括1.在那两个陈述中 由序列点分开。想想两个修改的线程 没有使用锁或类似的东西的相同数据。

      

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

    该对象只允许修改一次

      

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

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

      

    行为未定义。

    3.中的“应”是含蓄地说的。所有“应该”导致UB如果 他们没有实现。

答案 1 :(得分:3)

  

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

如果您仔细阅读标准报价

  

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

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

  

如果对标量对象的副作用相对于(对相同的标量对象的不同副作用)或(使用相同的标量对象)。

这意味着foo = ++i是一个已定义的语句。确实,i(也在foo上)也存在副作用,但此处对象i没有任何未排序

答案 2 :(得分:2)

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


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

  

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

一般来说,在6.5.16 / 3

中有保证
  

更新左操作数的存储值的副作用是   在左右操作数的值计算之后排序。评估   操作数没有排序。

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

  

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

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

  

在所有情况下,分配都在值之后排序   计算右和左操作数,并在赋值表达式的值计算之前。

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

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

答案 3 :(得分:1)

据我所知,

  

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

由于(1)表明

不适用于此处

  

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

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