【问题标题】:Sequence points and side effects in CC中的序列点和副作用
【发布时间】:2013-07-02 15:51:48
【问题描述】:

在这个C-FAQ 中给出关于sequence point 的信息;

标准规定:
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。此外,只能访问先验值以确定要存储的值。

在例子中

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

从声明的第一句话可以清楚地看出,这些示例是未定义行为的结果。
在解释该声明的第二句话时,据说;

第二句说:如果在一个完整的表达式中写入一个对象,那么在同一个表达式中对它的任何和所有访问都必须直接参与到要写入的值的计算中。该规则有效地将合法表达限​​制为那些访问明显先于修改的表达。例如旧的备用

 i = i + 1 

是允许的,因为 i 的访问用于确定 i 的最终值。例子

a[i] = i++

是不允许的,因为 i 的访问之一(a[i] 中的那个)与最终存储在 i 中的值无关(这在 i++ 中发生),所以没有定义的好方法。

我的问题是;
1.这是什么意思,如果在一个完整的表达式中写入一个对象,那么在同一个表达式中对它的任何和所有访问都必须直接参与到要写入的值的计算中。 ?

2.什么意思,例子a[i] = i++ 被禁止,因为 i 的访问之一(a[i] 中的那个)与最终存储在 i 中的值无关(这在 i++ 中发生)
有人可以用一些简单的方式解释一下吗?

【问题讨论】:

    标签: c undefined-behavior sequence-points


    【解决方案1】:

    我的问题是; 1.这是什么意思,如果在一个完整的表达式中写入一个对象,那么在同一个表达式中对它的任何和所有访问都必须 直接参与要写入的值的计算。?

    使用i++ 之类的子表达式,会写入i。此外,赋值是一个表达式,所以在i = 2 中,i 被写入。 a = b 是一个表达式可能不是很明显,但它确实是。这就是为什么你可以做类似a = b = c 的事情,这很好,if (a = b) 不太好。

    所以它的意思是,如果你写信给i,使用=,或者前或后增量,那么对i 的任何访问都必须作为i 的新值计算的一部分。然而,这一点很重要,在计算前后增量时唯一涉及的是i在语句开头的值。

    2.这是什么意思,示例 a[i] = i++ 是不允许的,因为其中一个访问 of i(a[i] 中的那个)与最终的值无关 存储在 i 中(发生在 i++ 中)

    正是它所说的。当您在a[i] 中访问i 时,它不属于i 的新值计算的一部分,它由i++ 产生。

    谁能用简单的方式解释一下?

    简单的方法:不要在表达式中使用前置或后置增量。总是在自己的语句中使用它们。如果您真的必须这样做,请不要在整个语句的其他任何地方使用相同的变量。

    【讨论】:

      【解决方案2】:

      这个解释很奇怪,我无法理解。

      真正的解释是表达式i++ 有一个副作用(递增i),可以在评估此特定i 后随时应用。

      由于在序列点之外,C 语言不保证评估顺序或应用后增量的时间(可能出于性能原因),在第二个示例中可能会发生三件事(假设在行前i = 5):

      • 首先评估最左边的ia[i] 中的那个),以计算a[i] 的地址用于存储。然后评估最右边的i,然后应用后增量:该行是执行a[5] = 5; i=6;
      • 首先计算最右边的i,然后是最左边的,然后i,当一切都说完后应用后增量:在这种情况下,效果与上述情况相同。
      • 首先计算最右边的i,立即应用后自增,然后计算最左边的i,计算a[i]的地址用于存储。这次的效果是a[6] = 5; i=6

      结果的选择不仅取决于编译器的选择,还取决于编译器的设置:Microsoft Visual C++ 可以根据您是在 Debug 还是在 Release 中编译给出不同的结果。

      【讨论】:

      • 你的解释是关于a[i] = i++的UB,你解释得很好,但我的问题需要解释。
      • 唉,我没有看到你引用的部分的解释。他们对我来说似乎很荒谬。
      • 您的解释听起来像是有少量可能的订购,其中一种会发生。这将对应于标准中的未指定行为的概念。标准所说的是a[i] = i++;未定义的行为(C99 中为 6.5:2),因此您的解释,无论您认为多么“正确”,并不等同于标准。
      • @Medinoc 有一个“非定义或未定义”的名称。这个名字是“未指定的”。在这个例子的上下文中,标准故意避免使用这个名称,而是使用“未定义的行为”。如果你编译或执行这个程序,这允许编译器让恶魔飞出你的鼻子。
      • @Medinoc:继续 Pascal 的评论,允许编译器假设您不编写调用 UB 的代码,从而相应地进行优化(等)。如果你确实编写了这样的代码,那么假设是无效的,结果可能完全出乎意料。
      【解决方案3】:

      最后我得到了一个关于这一点的explanation。在阅读它和FAQ 我得出结论之后;

      1.最后一句话

      此外,之前的值只能被访问以确定要存储的值

      会是这样的;

      此外,对象的先前值只能被访问以确定修改/新值(同一对象 ) 被存储。

      这个例子很清楚

       int i = 1, j, a[5];    
       i = i + 1;
       j = i + 1;
       a[i] = i; 
      

      在表达式i = i + 1 的情况下,i(在 RHS 中)的先前值(这里是 1)被访问以确定要存储的 i 的值,这就是语句

      如果在完整表达式中写入对象,则在同一表达式中对其的任何和所有访问都必须直接参与要写入的值的计算 .

      说。
      而在j = i + 1a[i] = i 的情况下,i 的访问值是 just value not prior value as no where @这些语句中修改了 987654331@。

      2.第二个问题可以解释为;
      在表达式a[i] = i++a[i++] = i的情况下,上述语句的第一句

      在上一个序列点和下一个序列点之间,一个对象的存储值最多只能通过表达式的评估修改一次。

      获取失败,因为i 在两个连续序列点之间只修改了一次。这就是为什么我们需要第二句话。
      这两个例子在 C 中都是不允许的,因为i 的先前值被访问了两次,即i++ 本身在表达式中访问iprior value 以修改它,因此其他访问i先前值/值 是不必要的,因为它不会被访问以确定要存储的修改后的值。

      【讨论】:

      • 嘿@hacks 根据您的第二个解释,您假设我们写入的值是'i',但实际上是a[i]....对我来说很困惑。请告诉我路径!
      • @Bayant_singh;在哪一行?
      • 在上一个序列点和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。
      • 请告诉我在哪里可以找到完整的参考资料
      • 表达式i++ 正在写入i。在a[i] = i++ 中,有两个写操作——一个是++(到i),一个是=(到a[i])。
      猜你喜欢
      • 2015-09-15
      • 2014-01-30
      • 2019-09-07
      • 2011-09-17
      • 1970-01-01
      • 2013-05-09
      • 2011-05-21
      • 2021-11-16
      • 1970-01-01
      相关资源
      最近更新 更多