Can the C compiler optimizer break a short circuit and change the memory access order for operands in a logical-and-expression?

We know that the logical operator AND ( && ) guarantees an estimate from left to right.

But I wonder if the compiler optimizer can ever reorder the memory access instructions for *a and b->foo in the following code, i.e. the optimizer writes instructions that try to access *b before accessing *a .

(Consider a and b as pointers to memory areas on the heap.)

 if (*a && b->foo) { /* do something */ } 

You might think that && calls a point in the sequence, so the compiler should issue instructions for accessing *a before accessing *b , but after reading the accepted answer at https://stackoverflow.com/a/16625/16/16 , I'm not sure. If you look at this answer, there are half-columns between the operators, and they also set the sequence points, and therefore they should also prevent reordering, but the answer there seems to indicate that they need a compiler level level barrier, despite having semicolons.

I mean, if you claim that && sets the sequence point, then this is true for the semicolon in the code https://stackoverflow.com/a/166268/169 . Then why does this code require a compiler-level security barrier?

+6
source share
4 answers

The system can evaluate b->foo until it reaches something that exceeds its ability to perform speculatively. Most modern systems can handle a speculative error and ignore the error if it turns out that the results of the operation are never used.

Thus, it depends only on the capabilities of the compiler, processor and other components of the system. As long as he can guarantee that there are no visible consequences for code compliance, he can do (almost) anything (almost) anything at any time.

+6
source

But I wonder if the compiler optimizer can ever change the order of the memory access commands for * a and b-> foo in the following code, that is, the optimizer writes instructions that try to access * b before accessing * a.

 if (*a && b->foo) { /* do something */ } 

The semantics of C for expression require that *a evaluated first, and b->foo evaluated only if *a evaluated as non-zero. The @Jack answer provides the basis for this in the standard. But your question is about optimizations that the compiler performs, and the standard states that

The semantic descriptions in this International Standard describe the behavior of an abstract machine in which optimization problems do not matter.

(C2013, 5.1.2.3/1)

An optimizing compiler can create code that does not conform to abstract semantics if it creates the same external behavior.

In particular, in your code example, if the compiler can prove (or want to assume) that the *a and b->foo estimates have no externally visible behavior and are independent - none of them have a side effect, affect the evaluation or side effects of the other - then it can emit a code that evaluates b->foo unconditionally, before or after evaluating *a . Note that if b is NULL or contains an invalid pointer value, then evaluating b->foo has undefined behavior. In this case, the rating b->foo is independent of any other rating in the program.

However, as @DavidSchwartz notes, even if the value of b can be null or invalid, the compiler may still be able to emit code that speculatively flows as if it were valid, and return if this does not work out case. The key point here is that the externally visible behavior does not depend on proper optimization.

+4
source

In accordance with the ISO standard C11, in ยง C, annex C, he stated that

Below are the points of the sequence described in ... Between the estimates of the first and second operands of the following operators: logical AND && (6.5.13); logical OR || (6.5.14); comma, (6.5.17).

And, as indicated in clause 5.1.2.3:

Segmented earlier is an asymmetric, transitive, pairwise relation between estimates performed by a single thread, which causes a partial order among these estimates. For any two estimates of A and B, if A is sequenced to B, then the execution of A must precede the execution of B.

Thus, it is guaranteed that the first operand will be evaluated to the second. In this case, safe optimization should not be possible.

+3
source

First of all, I believe that && means an embedded version of the logical AND operator.

I think that the compiler can legitimately perform evaluations from a sub-expression of the right side of the && operator before it completes the evaluation of the left side, but in a way that does not alter the semantics of the full expression.

For your example, the C ++ compiler is allowed to introduce reordering under the following conditions:

  • a is a primitive pointer (i.e. its type is not a class that overloads operator* ).
  • b is a primitive pointer (i.e. its type is not a class that overloads operator-> )
  • b , as you know, is sought regardless of the value of *a

If 1. fails, then the user operator* may have the side effect of changing the value of b->foo .

If 2. is not executed, then the user-defined operator-> can change the value of *a or throw away or produce another observable side effect (for example, print something) that should not be displayed had *a evaluated as false .

If 3. cannot be proved using static analysis, then reordering will result in undefined behavior that is not in the original program.

Compiler

C, naturally, has to execute only the 3rd check.

In fact, even if *a and b->foo are associated with operator overloading, the C ++ compiler can still reorder the instructions when these statements can be embedded and the compiler does not detect anything dangerous.

+3
source

All Articles