How to determine what is "sequenced before"?

I went through this excellent answer regarding Undefined Behavior and Sequential Interaction [Before / After] in C ++ 11. I understand the concepts of binary relations, but I don’t understand what the new rules that define sequence are.

For these familiar examples, how do the sequence rules apply new ?

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

In particular, what are the new C ++ 11 sequencing rules?

I'm looking for some rules like (this one is completely drafted)

The operator lhs operator '=' always sequenced before rhs and, therefore, is evaluated first.

In case they are available in the standard itself, can someone quote the same here?

+20
c ++ language-lawyer c ++ 11
Mar 05 2018-12-12T00:
source share
2 answers

Sequenced relationships and the rules related to this are the β€œordering” of previous rules about sequence points that are consistently consistent with other relationships of the memory model, such as occurring earlier and synchronized, you can precisely determine which operations and effects are visible under what circumstances.

The consequences of the rules are not changed for simple single-threaded code.

Start with your examples:

1. i = ++i;

If i is a built-in type, such as int , then there are no functions involved, all this is a built-in operator. So there are 4 things:

(a) Calculation of the value ++i , which is the original value-i +1

(b) A side effect of ++i that keeps the original value of -i +1 back to i

(c) Calculation of the assignment value, which is only the stored value, in this case, the result of calculating the value ++i

(d) A side effect of the assignment that stores the new value in i

All these things are sequenced - before the next full expression. (i.e. all of them are terminated by an operator endpoint)

Since ++i equivalent to i+=1 , the side effect of storing the value is sequenced - before calculating the value of ++i , therefore (b) is sequenced before (a).

The calculation of the values ​​of both assignment operands is sequenced - before calculating the value of the assignment itself and, in turn, is ordered - before the side effect of storing the value. Therefore, (a) sequenced to (c) and (c) sequenced to (g).

Therefore, we have (b) β†’ (a) β†’ (c) β†’ (d), and this, therefore, is OK in accordance with the new rules, while in C ++ 98 there was no OK.

If i was a class , then the expression would be i.operator=(i.operator++()) or i.operator=(operator++(i)) , and all the effects of calling operator++ sequenced - before calling operator= .

2. a[++i] = i;

If a is an array type and i is int , then again the expression has several parts:

(a) Calculation of i

(b) Calculation of ++i

(c) A side effect of ++i that saves the new value back to i

(d) Computing the value of a[++i] , which returns the value of l for the element a , indexed by computing the value of ++i

(e) The calculation of the assignment value, which is only the stored value, in this case is the result of calculating the value of i

(f) A side effect of an assignment that stores a new value in an array element a[++i]

Again, all these things are sequenced - before the next full expression. (i.e. all of them are terminated by an operator endpoint)

Again, since ++i equivalent to i+=1 , the side effect of storing the value is sequenced - before calculating the value of ++i , therefore (c) is sequenced before (b).

The calculation of the index value of the ++i array is * sequenced-before` the calculation of the element selection value, therefore (b) is sequenced-up to (d).

The calculation of the values ​​of both assignment operands is sequenced - before calculating the value of the assignment itself and, in turn, is ordered - before the side effect of storing the value. Therefore, (a) and (d) are sequenced to (e) and (e) sequenced to (f).

Therefore, we have two sequences: (a) β†’ (d) β†’ (e) β†’ (f) and (c) β†’ (b) β†’ (d) β†’ (e) β†’ (f).

Unfortunately, there is no order between (a) and (c). Thus, the side effect that is stored before i does not affect the calculation of the value on i , and the code demonstrates undefined behavior. This is again given by 1.9p15 of the C ++ 11 standard.

As above, if i has a class type, then everything is fine, because operators become calls to functions that impose a sequence.

rules

The rules are relatively simple:

  • The calculation of the values ​​of the arguments of the built-in operator is sequenced - before calculating the value of the operator itself.

  • Side effects of the built-in assignment operator or preincrement operator are sequenced - before calculating the result value.

  • The calculation of the values ​​of any other built-in operator is sequenced - before the side effects of this operator.

  • The calculation of values and side effects of the left side of the built-in comma operator are sequenced - before calculating the values and side effects of the right side.

  • All calculation calculations and side effects of the full expression are sequenced - before the next full expression.

  • The calculation of the value and side effects of the function call arguments are sequenced before the first full expression in the function.

  • The calculation of the value and side effects of everything inside the function are sequenced - before calculating the value of the result.

  • For any two function calls in full expression, either the calculation of the result value of one is sequenced - before calling the other, or vice versa. If no other rule indicates the order, the compiler can choose.

    Thus, in a()+b() either a() sequenced to b() or b() sequenced to a() , but there is no rule to indicate which one.

  • If there are two side effects that change the same variable, and none of them are sequenced - before the other, the code has undefined behavior.

  • If there is a side effect that modifies a variable and calculates the value that reads this variable, and none of them are sequenced - before the other, the code has undefined behavior.

+13
Mar 06 2018-12-12T00:
source share

This, in my opinion, is a much more complicated rule than the old rule of sequence points, and I am not 100% positive. I understood this correctly ... in any case, IIUC all comes down to getting the value you need that the side effect has already been applied.

First case

 i = ++i; 

Here, to complete the task, you need the value of the right-hand side, and to obtain this value, you need to have the side effect already applied; therefore, here the assignment is performed after the increment, and everything is in order. The important point here is that in order to complete the task you need the RHS value and only the LHS address .

Repeat:

  • assigned sequence after &i and ++i
  • ++i sequenced after increment
  • (transitivity) is assigned the sequence after the increment

The value of i is read only once after the increment. It is recorded twice, once in increments and once by destination, but these two operations are sequenced (first increment, then destination).

Second case

 a[++i] = i; 

Here, instead, you need an i value for RHS and a ++i value for LHS. However, these two expressions are not sequenced (the assignment operator does not overlap the sequence), so the result is undefined.

Repeat:

  • assignment sequenced after &a[++i] and i
  • &a[++i] sequenced after ++i
  • ++i sequenced after increment

Here, the value of i is read twice, once for the LHS assignment and once for RHS. The LHS part also performs the modification (increment). This write access and read access to the RHS assignments, however, are not sequenced relative to each other, and therefore this expression is UB.

Final ranting

Let me reiterate that I am not sure what I just said ... my strong opinion is that this new sequenced approach before and after is much more difficult to understand. The new rules, I hope, only made some expressions that were UB so far clearly defined (and UB is the worst possible result), but it also made the rules much more complex (it was just not to change the same thing twice between points sequences "... you did not need to do a mental topological sorting to guess if something was UB or not).

In a sense, the new rules did not harm C ++ programs (UB is the enemy, and now there is less UB in this area), but it damaged the language by increasing complexity (and, for sure, something that C ++ did not require was added complexity).

Note also that the fun thing about ++i is that the return value is a value of l (which is why ++ ++ i is legal), so it is basically an address and it is not logically required that the return value be ordered after incrementing . But the standard says so, and this is the rule that you need to burn in your neurons. Of course, in order to have "useful" ++i , you want users of the value to receive an updated value, but still, as far as the ++ operator sees things (it returns an address that is not affected by the increment), this sequence is not formally required.

With the new rules, you need to not only do a mental topological sorting to make sure the expression is valid, but you also need to do this using arbitrary sequence relationships that you just need to remember.

Although you, as a programmer, hope never to write code that changes the same value several times without a crystal clear sequence, you will still encounter errors in the code written by other programmers ... where things are not as clear , and now you need to think that you just need to understand that something is legitimate C ++ or not.

+6
Mar 05 2018-12-12T00:
source share



All Articles