Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowed compiler optimizations on loops in C++11

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?

like image 403
Martin Avatar asked Mar 03 '13 15:03

Martin


2 Answers

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 thread T at a particular point is the initial value of the object, a value assigned to the object by T, 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.

Bootnote

[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.

like image 74
Lightness Races in Orbit Avatar answered Sep 20 '22 07:09

Lightness Races in Orbit


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.

like image 40
Raedwald Avatar answered Sep 19 '22 07:09

Raedwald