CPU乱序执行是否影响C++ new运算符顺序?多线程下该单例场景是否可能?
问题:CPU乱序执行下,new是否会返回未完成构造的对象指针?
背景
C++中new运算符的执行流程:
- 分配内存:在堆上为单个对象或对象数组分配足够容纳对应类型的内存空间。
- 初始化对象:内存分配完成后,调用构造函数初始化对象;数组场景下为每个元素调用构造函数。
- 返回指针:返回指向对象类型的指针,指向已分配内存的首字节;数组则返回指向第一个元素的指针。
相关Singleton代码
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_; }
场景描述
线程A获取锁后执行
new Singleton(),尚未完成构造函数的执行,但指针已指向申请的内存。此时线程B进入,在第一个if (pinstance_ == nullptr)判断中发现指针非空,便使用该指针进行后续处理。
结论与解释
这种情况完全可能发生,原因如下:
在C++11之前的标准中,并没有对new操作的内存顺序做强制约束,CPU的乱序执行优化可能会重排pinstance_ = new Singleton()的底层执行步骤:
- 先完成堆内存的分配
- 将分配到的内存地址直接赋值给
pinstance_ - 最后执行对象的构造函数
这就导致线程A还在执行构造函数时,pinstance_已经被设置为非空值。此时线程B进入GetInstance(),第一个空指针检查会直接通过,返回这个指向未完全构造对象的指针,后续对该指针的访问会触发未定义行为。
解决方案
C++11及之后的标准已经修复了new操作的顺序问题,明确要求「内存分配 → 对象构造 → 指针返回」的顺序不能被重排。但即使如此,原代码中的双重检查锁(DCLP)仍存在内存可见性问题,因为pinstance_是普通静态指针,线程间的内存同步没有保障。
更可靠的实现方式有两种:
- 将
pinstance_声明为原子类型:
static std::atomic<Singleton*> pinstance_;
原子操作会强制内存顺序,避免乱序和可见性问题。
- 使用局部静态变量(C++11及以上推荐):
static Singleton* GetInstance() { static Singleton instance; return &instance; }
C++11标准规定,局部静态变量的初始化是线程安全的,编译器会自动保证初始化过程的原子性和有序性,无需手动加锁,实现更简洁可靠。
内容的提问来源于stack exchange,提问作者Hee
相关产品推荐
相关产品推荐

