You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()的底层执行步骤:

  1. 先完成堆内存的分配
  2. 将分配到的内存地址直接赋值给pinstance_
  3. 最后执行对象的构造函数

这就导致线程A还在执行构造函数时,pinstance_已经被设置为非空值。此时线程B进入GetInstance(),第一个空指针检查会直接通过,返回这个指向未完全构造对象的指针,后续对该指针的访问会触发未定义行为。

解决方案

C++11及之后的标准已经修复了new操作的顺序问题,明确要求「内存分配 → 对象构造 → 指针返回」的顺序不能被重排。但即使如此,原代码中的双重检查锁(DCLP)仍存在内存可见性问题,因为pinstance_是普通静态指针,线程间的内存同步没有保障。

更可靠的实现方式有两种:

  1. 将pinstance_声明为原子类型:
static std::atomic<Singleton*> pinstance_;

原子操作会强制内存顺序,避免乱序和可见性问题。

  1. 使用局部静态变量(C++11及以上推荐):
static Singleton* GetInstance() {
    static Singleton instance;
    return &instance;
}

C++11标准规定,局部静态变量的初始化是线程安全的,编译器会自动保证初始化过程的原子性和有序性,无需手动加锁,实现更简洁可靠。

内容的提问来源于stack exchange,提问作者Hee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 00:33:35