libstdc++ 中 std::shared_ptr 的内存开销

std::shared_ptr<T> 的内存开销

std::shared_ptr<T>
    element_type*    _M_ptr;          // Contained pointer. sizeof(intptr_t) 字节
    __shared_count<_Lp>  _M_refcount; // Reference counter. sizeof(intptr_t) 字节
        _Sp_counted_base<_Lp>*  _M_pi;
std::_Sp_counted_base<__default_lock_policy>
    // vtable pointer               // sizeof(intptr_t) 字节
    _Atomic_word  _M_use_count;     // #shared                4 字节,实际上是 int 类型
    _Atomic_word  _M_weak_count;    // #weak + (#shared != 0) 4 字节

其中,记录 use count 是为了判断什么时候可以释放共享指针指向的对象;记录 weak count 是为了判断什么时候可以安全释放控制块本身。即便是共享指针指向对象已经被释放(use count 归零),也可能有弱指针会尝试转换成共享指针,因此应该保证这些弱指针能安全查询控制块。还有一点,如果用 std::make_shared 创建共享指针,use count 归零而 weak count 不归零时,共享对象只是被析构,其内存会等到 weak count 归零时一起被释放。可以参考 https://stackoverflow.com/a/49585948/

在 64 位环境下,一个 std::shared_ptr<T> 是 16 字节(8 + 8),其中控制块的真实大小是 16 字节(4 + 4 + 8)。(在 std::shared_ptr<T> 中存储的是指向控制块的指针、而不是控制块本身,这样才能保证使用相同的控制块。)

std::_Sp_counted_base<_Lp> 的大小

不过,std::_Sp_counted_base 还支持其他的上锁策略,从 /usr/include/c++/12/bits/shared_ptr_base.h 的以下代码可以看出一共有 4 种可选的上锁类型。

  using __gnu_cxx::_Lock_policy;
  using __gnu_cxx::__default_lock_policy;
  using __gnu_cxx::_S_single;
  using __gnu_cxx::_S_mutex;
  using __gnu_cxx::_S_atomic;

其中 __gnu_cxx::_S_mutex 会导致 std::_Sp_counted_base<_Lp> 继承的基类 _Mutex_base<_Lp> 从 0 字节(空基类优化)变成 40 字节(在 64 位环境下),此时 std::_Sp_counted_base<__gnu_cxx::_S_mutex> 为 56 字节。这种类型会在两个用来计数的变量(_M_use_count_M_weak_count)不能使用原子操作时被启用:此时这两个计数变量就只负责计数,同步用 mutex 来保证。在 x86 上 int 类型是可以支持原子操作的,所以默认模式不是 __gnu_cxx::_S_mutex

  // Empty helper class except when the template argument is _S_mutex.
  template<_Lock_policy _Lp>
    class _Mutex_base
    {
    protected:
      // The atomic policy uses fully-fenced builtins, single doesn't care.
      enum { _S_need_barriers = 0 };
    };

  template<>
    class _Mutex_base<_S_mutex>
    : public __gnu_cxx::__mutex
    {
    protected:
      // This policy is used when atomic builtins are not available.
      // The replacement atomic operations might not have the necessary
      // memory barriers.
      enum { _S_need_barriers = 1 };
    };

在支持整数原子操作的平台,标题中的类型的大小为 4 + 4 + sizeof(intptr_t) /* vptr */;在不支持整数原子操作的平台,此大小为 4 + 4 + sizeof(intptr_t) + sizeof(std::mutex)