Is a C++11-compliant compiler allowed to optimize/transform this code from:
bool x = true; // *not* an atomic type, but suppose bool can be read/written atomically
/*...*/
{
while (x); // spins until another thread changes the value of x
}
to anything equivalent to an infinite loop:
{
while (true); // infinite loop
}
The above conversion is certainly valid from the point of view of a single-thread program, but this is not the general case.
Also, was that optimization allowed in pre-C++11?
Absolutely.
Since x
is not marked as volatile
and appears to be a local object with automatic storage duration and internal linkage, and the program does not modify it, the two programs are equivalent.
In both C++03 and C++11 this is by the as-if rule, since accessing a non-volatile object is not considered to be a "side effect" of the program:
[C++11: 1.9/12]:
Accessing an object designated by a volatile glvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression (or a sub-expression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects. When a call to a library I/O function returns or an access to a volatile object is evaluated the side effect is considered complete, even though some external actions implied by the call (such as the I/O itself) or by the volatile access may not have completed yet.
C++11 does make room for a global object to have its value changed in one thread then that new value read in another:
[C++11: 1.10/3]:
The value of an object visible to a threadT
at a particular point is the initial value of the object, a value assigned to the object byT
, or a value assigned to the object by another thread, according to the rules below.
However, if you're doing this, since your object is not atomic:
[C++11: 1.10/21]:
The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.
And, when undefined behaviour is invoked, anything can happen.
[C++11: 1.10/25]:
An implementation should ensure that the last value (in modification order) assigned by an atomic or synchronization operation will become visible to all other threads in a finite period of time.
Again, note that the object would have to be atomic (say, std::atomic<bool>
) to obtain this guarantee.
The compiler is allowed go do anything to those two loops. Including terminating the program. Because infinite loops have undefined behaviour if they do not perform a synchronization-like operation (do something that requires synchronization with another thread or I/O), according to the C++ memory model:
Note that it means that a program with endless recursion or endless loop (whether implemented as a for-statement or by looping goto or otherwise) has undefined behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With