We know that logical-AND operator (&&
) guarantees left-to-right evaluation.
But I am wondering if the compiler optimizer can ever reorder the me
The system can evaluate b->foo
until such time as it hits something that exceeds its ability to execute speculatively. Most modern systems can handle a speculative fault and ignore the fault if it turns out that the results of the operation are never used.
So it's purely up to the capabilities of the compiler, CPU, and other system components. So long as it can ensure there are no visible consequences to conforming code, it can execute (almost) anything it wants (almost) any time it wants.
But I am wondering 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.
if (*a && b->foo) { /* do something */ }
The C semantics for the expression require that *a
be evaluated first, and that b->foo
be evaluated only if *a
evaluated to nonzero. @Jack's answer provides the basis for that in the standard. But your question is about optimizations that compiler performs, and the standard specifies that
The semantic descriptions in this International Standard describe the behavior of an abstract machine in which issues of optimization are irrelevant.
(C2013, 5.1.2.3/1)
An optimizing compiler can produce code that does not conform to the abstract semantics if it produces the same external behavior.
In particular, in your example code, if the compiler can prove (or is willing to assume) that the evaluations of *a
and b->foo
have no externally visible behavior and are independent -- neither has a side effect that impacts the evaluation or side effects of the other -- then it may emit code that evaluates b->foo
unconditionally, either 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 that case, evaluation of b->foo
is not independent of any other evaluation in the program.
As @DavidSchwartz observes, however, even if b
's value may be null or invalid, the compiler may still be able to emit code that speculatively proceeds as if it were valid, and backtracks in the event that that turns out not to be the case. The key point here is that the externally-visible behavior is unaffected by valid optimizations.
First of all I take it as granted that &&
stands for the built-in version of the logical AND operator.
I think that the compiler may legitimately perform evaluations from the right-hand-side sub-expression of the &&
operator before it completes evaluation of the left-hand side, but in a manner that wouldn't change the semantics of the full expression.
For your example, 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
is known to be dereferenceable regardless of the value of *a
If 1. doesn't hold, then the user-defined operator*
may have a side effect of changing the value of b->foo
.
If 2. doesn't hold, then the user-defined operator->
may change the value of *a
, or throw, or produce another observable side-effect (e.g. print something) that shouldn't have shown up had *a
evaluated to false
.
If 3. cannot be proved through static analysis, then reordering would introduce undefined behaviour that is not in the original program.
C compiler, naturally, only needs to perform the 3rd check.
In fact, even if *a
and b->foo
involve operator overloading, C++ compiler can still reorder some instructions when those operators can be inlined and the compiler doesn't detect anything dangerous.
According to the C11 ISO standard, at §C, annex C, it's stated that
The following are the sequence points described in ... Between the evaluations 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 stated in §5.1.2.3:
Sequenced before is an asymmetric, transitive, pair-wise relation between evaluations executed by a single thread, which induces a partial order among those evaluations. Given any two evaluations A and B, if A is sequenced before B, then the execution of A shall precede the execution of B.
So it's guaranteed that the first operand is evaluated before the second. No safe optimization should be possible in this circumstance.