评估顺序和未定义的行为 [英] Order of evaluation and undefined behaviour

查看:131
本文介绍了评估顺序和未定义的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在C ++ 11标准(不再有一个序列点的概念,如你所知)的上下文中,我想了解如何定义两个最简单的例子。

  int i = 0; 

i = i ++; //#0

i = ++ i; //#1

SO上有两个主题,解释C ++ 11上下文中的这些示例。 这里说,#0 调用UB和#1 是明确定义的。 这里说,两个示例都是未定义的。这种模糊性让我很困惑。我已经阅读了这个结构良好的参考三次,但这个主题似乎太复杂了



让我们分析一下例子#0 i = i ++;



相应的引号是:



内置后增量和后期增值的价值计算
运算符在其副作用之前进行排序。
  • 内置
    赋值运算符和所有内置复合赋值运算符
    的副作用(左参数的修改)在
    的左侧和右侧参数的值计算(但不是副作用)之后,并且在值
    之前对赋值表达式计算(即,在返回
    之前的引用修改的对象)


  • 如果对标量对象的副作用相对于对同一标量对象的另一个
    副作用无效,未定义。



  • 在我看来,赋值运算符的副作用是没有排序的副作用它的左右参数。因此,赋值运算符的副作用不会用 i ++ 的副作用排序。因此#0 调用UB。





    让我们分析示例#1 i = ++ i;



    相应的引号是:



    • 内置prebrement和predecrement
      运算符在其值计算之前被排序(隐含规则由
      定义为复合赋值)


    • 副作用内部
      赋值运算符和所有内置复合赋值运算符
      的左侧参数的修改(左侧参数的修改)在左侧
      的值计算(但不是副作用)之后排序和右参数,并且在赋值表达式的值
      的计算之前排序(即,在返回
      之前引用修改的对象)


    • 如果对标量对象的副作用相对于对同一标量对象的另一个
      副作用没有影响,则该行为是未定义的。



    我看不到,这个例子与#0 不同。这似乎是一个UB与我一样的原因与#0 。分配的副作用没有按照 ++ i 的副作用排序。它似乎是一个UB。上面的话题说明它是很明确的。为什么?





    问题 :我可以应用引用的规则来确定示例的UB。一个尽可能简单的解释将非常感谢。谢谢!

    解决方案

    由于您的引用并非直接来自标准,我会尝试详细回答引用相关部分的标准。 副作用和评估的定义见第1.9 / 12节:


    访问由volatile glvalue 3.10),修改对象,调用库I / O函数或调用执行任何这些操作的函数都是副作用,这是执行环境的状态的变化。表达式(或子表达式)的评估一般包括两个值计算(包括确定用于glvalue计算的对象的标识和获取先前分配给用于prvalue计算的对象的值)和启动的副作用。


    下一个相关部分是第1.9 / 15段:



    < blockquote>

    除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值不受影响。 [...]运算符的操作数的值计算在运算符结果的值计算之前排序。如果对标量对象的副作用相对于对同一标量对象的另一个副作用或使用相同标量对象的值的计算没有序列,则该行为是未定义的。




    现在让我们来看看如何应用这两个例子。

      i = i ++; 

    这是增量的后缀形式,您可以在第5.2.6节中找到它的定义。最相关的句子是:


    ++表达式的值计算在操作数对象的修改
    之前排序。


    对于赋值表达式,请参见第5.17节。相关部分说明:


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


    使用上面的所有信息,整个表达式的求值是标准!):




    • i ++ 的值计算(右侧) / li>
    • i (左侧)的值计算

    • 修改 ++ 的副作用)

    • 修改 i = 的副作用)



    标准保证是两个操作数的值计算在赋值表达式的值计算之前排序。但是右侧的值计算只是读取 i 的值和修改 i ,这两个修改(副作用)没有相互排序,我们得到未定义的行为。



    第二个例子呢? p>

      i = ++ i; 

    情况在这里有很大的不同。你在第5.3.2节中找到前缀增量的定义。相关部分是:


    如果x不是bool类型,则表达式++ x等效于x + = 1。


    换句话说,我们的表达式等同于

      i =(i + = 1)

    查找复合赋值运算符 + = 在5.17 / 7中我们得到 i + = 1 等效于 i = i + 1 ,除了 i 只计算一次。因此,所涉及的表达式最终变成


    i =(i =(i + 1))


    但是从上面我们已经知道, = 的值计算在操作数的值计算之后排序,在 = 的值计算之前对副作用进行排序。因此,我们得到了明确的评估顺序:


    1. 计算 i + 1的值 i - 内部表达式的左侧)(#1)

    2. 启动内部的副作用 = ,即修改inner i

    3. (i = i + 1),这是 i

    4. 的新外部效果 = ,即修改outer i






    (#1):这里, i 只计算一次,因为 i + = 1 等价于 i = i + 1 ,除了 i 只计算一次(5.17 / 7)。


    Speaking in the context of the C++11 standard (which no longer has a concept of sequence points, as you know) I want to understand how two simplest examples are defined.

    int i = 0;
    
    i = i++;   // #0
    
    i = ++i;   // #1
    

    There are two topics on SO which explain those examples within the C++11 context. Here it was said that #0 invokes UB and #1 is well-defined. Here it was said that both examples are undefined. This ambiguity confuses me much. I've read this well-structured reference three times already but the topic seems to be way too complicated for me.

    .

    Let's analyze the example #0: i = i++;.

    Corresponding quotes are:

    • The value computation of the built-in postincrement and postdecrement operators is sequenced before its side-effect.

    • The side effect (modification of the left argument) of the built-in assignment operator and of all built-in compound assignment operators is sequenced after the value computation (but not the side effects) of both left and right arguments, and is sequenced before the value computation of the assignment expression (that is, before returning the reference to the modified object)

    • If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.

    As I get it, the side effect of the assignment operator is not sequenced with side effects of it's left and right arguments. Thus the side effect of the assignment operator is not sequenced with the side effects of i++. So #0 invokes an UB.

    .

    Let's analyze the example #1: i = ++i;.

    Corresponding quotes are:

    • The side effect of the built-in preincrement and predecrement operators is sequenced before its value computation (implicit rule due to definition as compound assignment)

    • The side effect (modification of the left argument) of the built-in assignment operator and of all built-in compound assignment operators is sequenced after the value computation (but not the side effects) of both left and right arguments, and is sequenced before the value computation of the assignment expression (that is, before returning the reference to the modified object)

    • If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.

    I can not see, how this example is different from the #0. This seems to be an UB for me for the very same reason as #0. The side effect of assignment is not sequenced with the side effect of ++i. It seems to be an UB. The topic liked above says it is well-defined. Why?

    .

    Question: how can I apply quoted rules to determine the UB of the examples. An as simple as possible explanation would be greatly appreciated. Thank you!

    解决方案

    Since your quotes are not directly from the standrad, I will try to give a detailed answer quoting the relevant parts of the standard. The definitions of "side effects" and "evaluation" is found in paragraph 1.9/12:

    Accessing an object designated by a volatile glvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression (or a sub-expression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects.

    The next relevant part is paragraph 1.9/15:

    Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

    Now let's see, how to apply this to the two examples.

    i = i++;
    

    This is the postfix form of increment and you find its definition in paragraph 5.2.6. The most relevant sentence reads:

    The value computation of the ++ expression is sequenced before the modification of the operand object.

    For the assignment expression see paragraph 5.17. The relevant part states:

    In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

    Using all the information from above, the evaluation of the whole expression is (this order is not guaranteed by the standard!):

    • value computation of i++ (right hand side)
    • value computation of i (left hand side)
    • modification of i (side effect of ++)
    • modification of i (side effect of =)

    All the standard guarantees is that the value computations of the two operands is sequenced before the value computation of the assignment expression. But the value computation of the right hand side is only "reading the value of i" and not modifying i, the two modifications (side effects) are not sequenced with respect to each other and we get undefined behavior.

    What about the second example?

    i = ++i;
    

    The situation is quite different here. You find the definition of prefix increment in paragraph 5.3.2. The relevant part is:

    If x is not of type bool, the expression ++x is equivalent to x+=1.

    Substituting that, our expression is equivalent to

    i = (i += 1)
    

    Looking up the compound assignment operator += in 5.17/7 we get that i += 1 is equivalent to i = i + 1 except that i is only evaluated once. Hence, the expression in question finally becomes

    i = ( i = (i + 1))

    But we already know from above that the value computation of the = is sequenced after the value computation of the operands and the side effects are sequenced before the value computations of =. So we get a well-defined order of evaluation:

    1. compute value of i + 1 (and i - left hand side of inner expression)(#1)
    2. initiate side effect of inner =, i.e. modify "inner" i
    3. compute value of (i = i + 1), which is the "new" value of i
    4. initiate side effect of outer =, i.e. modify "outer" i
    5. compute value of full expression.


    (#1): Here, i is only evaluated once, since i += 1 is equivalent to i = i + 1 except that i is only evaluated once (5.17/7).

    这篇关于评估顺序和未定义的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆