Can out-of-order execution of CPU affect the order of new operator in C++?

110 views Asked by At

The new operator in C++ performs the following actions:

  1. Allocates Memory: It allocates memory on the heap for a single object or an array of objects. The amount of memory allocated is enough to hold the object(s) of the specified type.

  2. Initializes Object(s): After allocating memory, new initializes the object(s) by calling their constructor(s). For a single object, it calls the constructor directly. For an array of objects, it calls the constructor for each object in the array.

  3. Returns a Pointer: It returns a pointer of the object's type that points to the first byte of the allocated memory where the object is stored. For an array, it returns a pointer to the first element of the array.

Whether a pointer has been returned, but the constructor has not fully executed with out-of-order execution of CPU

class Singleton
{

private:
    static Singleton * pinstance_;
    static std::mutex mutex_;

protected:
    Singleton()
    {
      //
    }
    ~Singleton() {}

public:

    Singleton(Singleton &other) = delete;

    void operator=(const Singleton &) = delete;

    static Singleton *GetInstance();
};

Singleton* Singleton::pinstance_{nullptr};
std::mutex Singleton::mutex_;

Singleton *Singleton::GetInstance()
{
    if (pinstance_ == nullptr)
    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (pinstance_ == nullptr)
        {
           pinstance_ = new Singleton();
        }
    }
    return pinstance_;
}

Thread A executes the new operator after getting the lock, and does not complete the constructor function, but the pointer has pointed to the requested memory. At this time, thread B comes in and finds that the pointer is not NULL in the first if judgment, and uses the pointer for subsequent processing

Is the above situation possible in the case of multithreading ?

2

There are 2 answers

0
Brian Bi On

If you write C++ code that has well-defined behavior according to the C++ standard, then you don't need to worry about the instructions being executed "out of order". In particular, the standard guarantees that in a new-expression, the allocation function will be called first, then, if the allocation is successful, the object will be initialized, and only after such initialization succeeds, the new-expression has a value and that value can be further used by the program. Thus, you can assume that your compiler will generate code that will make the program behave according to this sequence of events.

In multithreaded programs, the guarantees offered by the C++ standard are weaker. A data race in a multithreaded program causes the program to have undefined behaviour, and the observed behaviour of the program can make it seem like instructions are executed out of order, or even more unpredictable results can occur. Moreover, even in a multithreaded program that does not have data races, it is not guaranteed that when thread A observes a side effect caused by thread B, thread A can also see all previous side effects caused by thread B, unless the appropriate synchronization operations are used. Again, if you need particular behaviour, it is your responsibility to write code that gives that behaviour according to the C++ standard, using the synchronization operations provided by the standard. If you do that, you don't need to think about what is happening at the machine level.

0
Peter Cordes On

The cardinal rule of OoO exec is that it must not break single-threaded code. As long as your code doesn't have data races between threads, it all just works as if asm instructions ran one at a time, in program order.

The as-if rule for CPU is pretty much the same concept as the as-if rule for compilers making asm from C++ source, but for the CPU the "observable behaviour" is defined by the ISA's memory model, rather than the C++ standard.