C++ 中的 new 运算符执行以下操作:
分配内存:它在堆上为单个对象或对象数组分配内存。分配的内存量足以容纳指定类型的对象。
初始化对象:分配内存后,new 通过调用对象的构造函数来初始化对象。对于单个对象,它直接调用构造函数。对于对象数组,它为数组中的每个对象调用构造函数。
返回指针:它返回对象类型的指针,该指针指向存储对象的已分配内存的第一个字节。对于数组,它返回指向数组第一个元素的指针。
是否已返回指针,但构造函数尚未完全执行且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(const std::string& value)
{
if (pinstance_ == nullptr)
{
std::lock_guard<std::mutex> lock(mutex_);
if (pinstance_ == nullptr)
{
pinstance_ = new Singleton();
}
}
return pinstance_;
}
线程A拿到锁后执行new操作符,并没有完成构造函数,但是指针已经指向了申请的内存。这时线程B进来,在第一次if判断中发现指针不为NULL,并使用该指针进行后续处理
在多线程的情况下,上述情况也可能吗?
如果您根据 C++ 标准编写具有明确定义行为的 C++ 代码,那么您无需担心指令“乱序”执行。特别是,标准保证在new-expression中,首先调用分配函数,然后,如果分配成功,则初始化对象,并且只有在初始化成功后,new-expression具有一个值,并且该值可以被程序进一步使用。因此,您可以假设您的编译器将生成使程序根据此事件序列运行的代码。
在多线程程序中,C++ 标准提供的保证较弱。多线程程序中的“数据竞争”会导致程序出现未定义的行为,并且观察到的程序行为可能会使指令看起来像是无序执行的,甚至可能会出现更不可预测的结果。而且,即使在没有数据竞争的多线程程序中,也不能保证当线程 A 观察到线程 B 引起的副作用时,线程 A 也能看到线程 B 引起的所有先前副作用,除非进行适当的同步操作被使用。同样,如果您需要特定的行为,则您有责任使用标准提供的同步操作,根据 C++ 标准编写提供该行为的代码。如果你这样做,你就不需要考虑机器层面发生了什么。
CPU 的 as-if 规则与从 C++ 源代码生成 asm 的编译器的 as-if 规则几乎是相同的概念,但对于 CPU 来说,“可观察的行为”是由 ISA 的内存模型定义的,而不是 C++ 标准.