What does “previous value, available only to determine the value to be maintained” mean?

From Prasoon's answer to the question “Undefined Behavior and sequence points,” I do not understand what the following means:

.. the previous value should be consulted only to determine the stored value.

The following are examples: Undefined Behavior in C ++:

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

Despite the explanations given here, I do not understand this part (I think I understand the rest of the answer correctly).


I do not understand what is wrong with the above code samples. I think they have clearly defined steps for the compiler, as shown below.

a[i] = i++;

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

int x = i + i++ ;

  • x = i + i;
  • i = i + 1;

What am I missing? What can only be accessed by 'to determine the value to be stored? mean?

+4
source share
4 answers

See also this question and my answer to it . I am not going to vote to close this as a duplicate, because you are asking about C ++ and not about C, but I believe that the problem in both languages ​​is the same.

access to the previous value is possible only to determine the stored value.

This seems like an odd claim; why is standard care about why access to a value is required? It makes sense when you understand that if the previous value is read to determine the value that should be stored in the same object, this implicitly imposes order on the two operations, so reading should happen before writing. Because of this ordering, two accesses to the same object (one read and one write) are safe. The compiler cannot reorder (optimize) the code so that it interferes with each other.

On the other hand, in an expression like

 a[i] = i++ 

There are three accesses to i : reading on the left to determine which element a should be changed, reading on the right to determine the value to be increased, and writing that saves the additional value to i . Reading and writing to RHS is okay ( i++ is safe in itself), but there is no definite order between reading on LHS and writing to RHS. Thus, the compiler can freely change the code in such a way as to change the relationship between these read and write operations, while the standard one figuratively throws up its hands and leaves the behavior undefined, not to mention the possible consequences.

Both C11 and C ++ 11 change the wording in this area, making some ordering requirements explicit. The "prior value" form no longer exists. Quote from the draft standard C ++ 11, 1.9p15:

Except where noted, estimates of the operands of individual operators and subexpressions of individual expressions are not affected. [...] Calculations of operator operand values ​​are sequenced before calculating the value of the operator result. If the side influence on the scalar object is independent of another effect on the same scalar object or the calculation of the value using the value of the same scalar object, the behavior is undefined.

+3
source
 a[i] = i++; 

i changed. i also read to determine which index a use, which does not affect storage on i . This is not allowed.

 int x = i + i++; 

i changed. i also used to calculate the value to store in x , which does not affect the storage on i . This is not allowed.

+3
source

Because the standard says that “the previous value should only be available to determine the value that you want to save,” compilers are not required to follow the “clearly defined” steps that you specify.

And they often do not.

What is the wording of the standard tool for your specific examples is that the compiler is allowed to perform the following steps:

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

int x = i + i++ ;

  • i = i + 1;
  • x = i + i;

This gives a completely different result than your imagined clearly defined order. The compiler is also allowed to do everything possible, even if it makes less sense to you than what I just printed above. What does undefined behavior mean.

+1
source

While an expression of type x=y+z; semantically equivalent to temp=y; temp+=z; x=temp; temp=y; temp+=z; x=temp; There is usually no requirement (if x is volatile ) for the compiler to implement it this way. On some platforms, this can be much more efficiently implemented as x=y; x+=z; x=y; x+=z; . If the variable is volatile , the code created by the compiler for the assignment can write any sequence of values ​​if:

  • Any code that has the right to read the "old" value of a variable acts on the value that existed before the assignment.

  • Any code that has the right to read the "new" value of a variable acts on the final value that it was given.

Given i=511; foo[i] = i++; i=511; foo[i] = i++; , the compiler will have the right to write the value 5 to foo[511] or foo[512] , but it is equally reliable to save it to foo[256] or foo[767] , or foo[24601] or anything else. Since the compiler has the right to store the value at any possible offset from foo , and since the compiler will have the right to do whatever it likes with code that adds too much offset to the pointer, these permissions together effectively mean that the compiler can do everything that it like using foo[i]=i++; .

Note that in theory, if i was a 16-bit unsigned int , but foo was an array of size 65536 or more (quite possibly on a classic Macintosh), the above rights would allow the compiler specified by foo[i]=i++; to write an arbitrary foo value but not do anything else. In practice, the Standard refrains from such subtle differences. It is much simpler to say that the standard does not impose any requirements on what compilers do when providing expressions like foo[i]=i++; than to say that compiler behavior is limited in some narrow edge cases, but not in others.

0
source

Source: https://habr.com/ru/post/1399619/


All Articles