none
Precedence issue RRS feed

  • Question

  • #include <iostream>	
    #include <iomanip>	
    #include <vector>	
    
    using std::vector;
    
    int main()
    {
    	unsigned bound{};
    
    	std::cout << "Enter a num: ";
    	std::cin >> bound;
    
    	vector<unsigned> values(bound);
    
    	for (size_t i{}; i < bound; )
    	{
    		values[i] = ++i;
    	}
    
    	for (auto value : values)
    		std::cout << value;
    
    }

    I run the code in Debug mode (C++ 14), and expect the following result in the first iteration:

    values[0] = 1

    values[1] = 0

    Because the precedence of [] precedes prefix ++.

    I know that informally, the C++17 edition of the standard added the rule that
    all side effects of the right side of an assignment (and this includes compound assignments, increments, and
    decrements) are fully committed before evaluating the left side and the actual assignment.

    But that is not the case, the compiler is set to C++14.

    The actual result is:

    values[0] = 0

    values[1] = 1




    • Edited by Refael1001 Tuesday, October 15, 2019 5:58 PM
    Tuesday, October 15, 2019 5:57 PM

Answers

  • Your example exhibits undefined behavior, but for different reasons in C++14 and earlier vs C++17 and later.

    This is the crux of the matter:

    [intro.execution]/15 Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced... 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.

    Before C++17, evaluations of the operands of assignment operator = are unsequenced, and so your program exhibits undefined behavior: there's a side effect ( ++i ) on an object and a value computation (in values[i] ) on the same object.

    C++17 specifies ( [expr.ass]/1 ) that evaluation of the right operand is sequenced before that of the left operand. So "values[i] = ++i;" is equivalent to "++i; values[i] = i;". Therefore, on the last iteration of the loop, the code accesses values[bound], which exhibits undefined behavior by way of accessing an index out of bounds.


    Igor Tandetnik

    • Proposed as answer by Guido Franzke Wednesday, October 16, 2019 6:14 AM
    • Marked as answer by Refael1001 Wednesday, October 16, 2019 6:26 AM
    Wednesday, October 16, 2019 12:38 AM

All replies

  • Your example exhibits undefined behavior, but for different reasons in C++14 and earlier vs C++17 and later.

    This is the crux of the matter:

    [intro.execution]/15 Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced... 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.

    Before C++17, evaluations of the operands of assignment operator = are unsequenced, and so your program exhibits undefined behavior: there's a side effect ( ++i ) on an object and a value computation (in values[i] ) on the same object.

    C++17 specifies ( [expr.ass]/1 ) that evaluation of the right operand is sequenced before that of the left operand. So "values[i] = ++i;" is equivalent to "++i; values[i] = i;". Therefore, on the last iteration of the loop, the code accesses values[bound], which exhibits undefined behavior by way of accessing an index out of bounds.


    Igor Tandetnik

    • Proposed as answer by Guido Franzke Wednesday, October 16, 2019 6:14 AM
    • Marked as answer by Refael1001 Wednesday, October 16, 2019 6:26 AM
    Wednesday, October 16, 2019 12:38 AM
  • Precedence influences the order in which operations are performed.  It has very little to do with the order in which operands are evaluated.  The standard does not specify which order  [i] and  ++i are evaluated.  Either could be first. 

    Furthermore, even when ++i is evaluated first, the side effect of storing the updated value back in i can occur any time before the next sequence point.  So it could occur before or after [i] is evaluated.

    Technically, your code exhibits undefined behavior.  See footnote 15 in paragraph 1.9 of C++14 standard (n3690).  In my opinion, the C11 standard (n1570) expresses the situation more clearly: "If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined." (section 6.5-2)

    • Proposed as answer by Guido Franzke Wednesday, October 16, 2019 6:14 AM
    • Marked as answer by Refael1001 Wednesday, October 16, 2019 6:18 AM
    • Unmarked as answer by Refael1001 Wednesday, October 16, 2019 6:27 AM
    Wednesday, October 16, 2019 12:47 AM
  • " The standard does not specify which order  [i] and  ++i are evaluated " Why not?

    Precedence

    Operator

    Description

    Associativity

    1

    ::

    Scope resolution

    Left-to-right

    2

    ++   --

    Suffix/postfix increment and decrement

    type() type{}

    Function-style type cast

    ()

    Function call

    []

    Array subscripting

    .

    Element selection by reference

    ->

    Element selection through pointer

    3

    ++   --

    Prefix increment and decrement

    Right-to-left

    +   -

    Unary plus and minus

    !   ~

    Logical NOT and bitwise NOT

    (type)

    C-style type cast

    *

    Indirection (dereference)

    &

    Address-of

    sizeof

    Size-of<sup>[note 1]</sup>

    newnew[]

    Dynamic memory allocation

    deletedelete[]

    Dynamic memory deallocation

    Wednesday, October 16, 2019 6:29 AM
  • Did you take into account the assignment operator's (=) precedence and associativity?

    16 a?b:c Ternary conditional<sup class="reference" id="cite_ref-2" style="line-height:1em;unicode-bidi:isolate;">[note 2]</sup> Right-to-left
    throw throw operator
    co_yield yield-expression (C++20)
    = Direct assignment (provided by default for C++ classes)
    +=   -= Compound assignment by sum and difference
    *=   /=   %= Compound assignment by product, quotient, and remainder
    <<=   >>= Compound assignment by bitwise left shift and right shift
    &=   ^=   |= Compound assignment by bitwise AND, XOR, and OR

    This precedence table states that in your expression the = is at a lower precedence than both [] and ++.

    So the thing here isn't the order of operations. Since both the [i] and the ++i are at a higher precedence than the = then both of them should be done by the time the assignment takes place. The problem here is your expectations with the [i].

    For whatever reason you seem to think that the compiler should capture the value of i so it assigns to values[0]. But your statement doesn't use a value, it uses a variable so it assigns to values[(whatever is stored in variable i)]. You then go and change the value stored in i due to the ++i expression. So the question is, especially with the assignment operator's right to left associativity, should the ++i affect the [i]?

    Changing multiple memory values in a single expression, or even a memory value multiple times, has always been a problematic and this is why the C++17 standard did clear this up a little bit. The C++17 standard took the approach that right to left parsing of an assignment is the correct way to go, so the expression values[i] = ++i; will increment i before the assignment takes place. 


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Wednesday, October 16, 2019 1:24 PM
  • Again, precedence doesn't determine the order of evaluation. If you have

    f() * g() + h()

    precedence says that the return value of f() should be multiplied by that of g(), and the result added to h(); as opposed to adding g() to h(), and multiplying that by f(). But it says nothing about the order in which f(), g() and h() are actually called. It's perfectly legal for the compiler to generate code that calls h() first, then g(), then f()  (or any other order, in fact), and then does the multiplication and the addition (in that order).


    Igor Tandetnik

    Wednesday, October 16, 2019 2:15 PM