【问题标题】:Sequence points and side effects: Quiet change in C11?序列点和副作用:C11 的悄然变化?
【发布时间】:2014-01-30 17:47:12
【问题描述】:

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 c99 undefined-behavior c11 sequence-points


【解决方案1】:

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


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

表达式++E等价于(E+=1)

对于一般分配,在 6.5.16/3 中有保证

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

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

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

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

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

(这对我来说更清楚,但我对 C++ 的了解远胜于 C)

i的修改是在i+=1的值计算之前排序的,所以我们没有UB访问foo = ++i++i的值(作为左右操作数的值计算) foo = xfoo的修改之前排序。

【讨论】:

    【解决方案2】:

    据我了解

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

    这里不适用,因为 (1) 指出

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

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

    【讨论】:

      【解决方案3】:

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

      如果你仔细阅读标准报价

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

      那么你会发现你的措辞应该是:

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

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

      【讨论】:

      • 两个副作用(递增i 和更新foo)是无序的,但这不会导致任何问题,因为它们是不同的对象。
      • 谢谢,我开始了解C11(2)。但是,C99 和 C11 在这里是否等价(唯一的证明,它不是,是一个反例)?
      • @KeithThompson;这两个副作用是无序的,但对于一个单独的对象(ifoo)没关系。这就是为什么我写了这里没有什么是无序的
      • @mafso;更新了我的答案。
      • @dyp:不,副作用可以在下一个序列点之前的任何时间发生。
      【解决方案4】:

      C11(以及 C++11)完全修改了排序的措辞,因为 C11 现在有线程,它必须解释访问相同数据的线程之间的排序意味着什么。该委员会的目的是在只有一个执行线程的情况下保持与 C99 的向后兼容。

      我们来看看C99版本:

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

      2. 一个对象

      3. 应该有

      4. 它的存储值最多修改一次

      5. 通过表达式的评估。

      与新文本相比

      如果有副作用

      4的不同术语,修改存储值

      标量对象

      对2中先前措辞的限制。新文本只说 关于标量对象的一些事情

      相对于任何一个都是无序的

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

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

      对象只允许修改一次

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

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

      行为未定义。

      3. 中的“应”含蓄地说。如果所有“应”通向 UB 它们没有实现。

      【讨论】:

        猜你喜欢
        • 2015-09-15
        • 2013-07-02
        • 2012-01-27
        • 2019-08-27
        • 1970-01-01
        • 1970-01-01
        • 2019-09-07
        • 2013-06-02
        • 2014-07-03
        相关资源
        最近更新 更多