3

Consider the following code:

Class* p = nullptr; //global var

This code executed by thread 1:

p = new Class;

This code executed on thread 2:

if (p != nullptr) ...; // does the standard gurantee that the pointer will be assigned only after object is constructed ?

My question is does the standard enforce when p will be assigned to point to allocated memory ? Example 1:

  • new expression call operator new
  • p is assigned to point to newly allocated memory
  • Class's c`tor is invoked and allocated memory is passed to it

Example 2:

  • new expression call operator new
  • Class's c`tor is invoked and allocated memory is passed to it
  • p is assigned to point to newly allocated memory
Aracthor
  • 5,757
  • 6
  • 31
  • 59
  • Why don't you put a sleep inside constructor and see yourself, and share the knowledge gained? :) – Ajay Jun 26 '15 at 07:42
  • According to http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf, the answer is "no" : you may have `p` pointing to allocated storage before the object has been constructed in said storage. – Quentin Jun 26 '15 at 07:46
  • There is no guarantee (in fact your example invokes undefined behaviour). This is precisely one of the reasons why p must be an atomic variable. The default ordering constraints of an atomic variable will also guarantee that the constructor is complete before being assigned to p. Herb Sutter's Atomic Weapons talk does a very good job of explaining this behaviour. – John5342 Jun 26 '15 at 07:56
  • Have a read about double locking pattern, the papers on it go into great length explaining why this does not work. – Alexander Balabin Jun 26 '15 at 07:58
  • @AlexanderBalabin Yep I've read about the locking patters, most of the examples I've seen for the double check locking pattern do the following: `Class* tmp = new class;` and after construction `pClass = tmp`; that why I've asked this question. – Alejandro Freeman Jun 26 '15 at 08:09
  • Related to [Is it necessary to lock an array that is *only written to* from one thread and *only read from* another?](http://stackoverflow.com/q/24682518/1708801) – Shafik Yaghmour Jun 26 '15 at 09:24

2 Answers2

6

According to the standard, if these operations in two threads aren't synchronized, the behavior is undefined.

C++11 draft N3337,

[intro.multithread]/4:

Two expression evaluations conflict if one of them modifies a memory location (1.7) and the other one accesses or modifies the same memory location.

[intro.multithread]/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.

The corresponding quote from C++14 is essentially the same.


As for the execution order of p = new Class;, it's like in your Example 2, because first new Class is evaluated, and then assignment happens (provided the constructor of Class or operator new didn't throw an exception).

[expr.ass]/1:

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • Okay, what about if we leave the second thread aside. Does the standard enforce when `p` will be assigned ? (before construction ends or before it begins) ? – Alejandro Freeman Jun 26 '15 at 08:03
0

No, the standard does not guarantee anything like this.

To fix it you need to have a memory barrier between construction of your object and assigning the pointer so that there is inter-thread happens-before relationship between them:

Class* tmp = new Class();
// you need a memory barrier here
p = tmp;

In c++11 you use std::atomic to introduce memory barriers:

std::atomic<Class*> p;

And in this case it is better to use store() rather than assignment:

p.store(tmp, std::memory_order_release);
Alexander Balabin
  • 2,055
  • 11
  • 13
  • This is wrong. *In all cases, the assignment is sequenced after the value computation of the right and left operands* - [expr.ass]/1 – Anton Savin Jun 26 '15 at 08:16
  • @Anton: this is what the C++ standard says, now how about the reordering CPU and the way memory is observed from another thread? If sequenced-after meant correct visibility order across threads we wouldn't need atomic in the language. – Alexander Balabin Jun 26 '15 at 08:27
  • Also if my understanding is correct the compiler is still allowed to reorder instructions even across "sequenced before" provided it can guarantee the same result observed in the current thread of execution. – Alexander Balabin Jun 26 '15 at 08:57
  • "Sequenced before" implies "happens before", (see N4140 [intro.multithread]/14). But yes, atomics with acquire-release semantics are needed to extend happens-before relation to another thread (and avoid data race which is UB). – Anton Savin Jun 26 '15 at 11:03
  • Would it be more correct to say that inter-thread happens-before is required between the actor and assignment? – Alexander Balabin Jun 26 '15 at 11:21