Android 平台智能指针使用与分析

2023年 9月 23日 68.5k 0

本文基于 aosp android12_r28 分支分析。

C/C++ 指针在使用上主要存在以下两类难点:

  • 野指针问题
    • C++ 的熟练工通常会把刚声明的指针赋值为 NULL。声明而未赋值的指针,有可能指向了一个未知的地址,成为野指针,操作这个指针可能会带来程序错误与崩溃。
    • 将对象 delete 后,C++ 的熟练工会将指针赋值为 NULL,不然这个指针也会成为野指针
    • 当多个指针都指向了对象 A,当某个地方将对象 A delete 后,操作其他地方的指针,就是对一个非法的内存进行操作了
  • 当使用了 malloc/new 分配了内存,都需要使用 free/delete 回收这块内存,当程序变得复杂起来,很多时候,程序员就会忘记回收内存,导致内存泄漏。

在 Android 中实现了一套完善的智能指针机制来简化 C++ 中指针的使用,以规避以上的两类难点。

1.智能指针的使用

在 Android 中,智能指针是一个类/对象,分为强引用 sp 与弱引用 wp,以下是 wp 与 sp 的使用示例:

// 需要使用智能指针的类必须继承自 RefBase
class X:public RefBase
{
    void test() 
    {
        //......
    }
}

int main()
{
    X* p = new X();

    //构建一个强引用
    sp spX(p);  // sp spX = new X()  这样也行,注意不要写成 sp spX = new sp() 
    //构建一个弱引用
    wp wpX(p); // 也可以 wp wpX(spX);

    //通过强引用使用对象
    spX->test();

    //通过弱引用使用对象
    //不能直接通过弱引用使用对象,升级为强指针再使用
    sp spX2 = wpx.promote()
    if(spX2 != NULL)  // spX2 与 NULL 不是同一类型, 这里重载了 !=,实际比较的是 spX2 内部指针与 NULL 是否相等 
    {
        spX2->test();
    }

   //离开引用作用域后,自动回收 p 指向的内存
}

强引用与一般意义的智能指针概念相同,通过引用计数来记录有多少使用者在使用一个对象,如果所有使用者都放弃了对该对象的引用,则该对象所在的内存将被自动回收。其使用方法如下:


// 0. 需要使用智能指针的类必须继承自 RefBase
class X:public RefBase
{
    void test() 
    {
        //......
    }
}

{   // 作用域开始
    // 1.被管理的指针
    X* p = new X();
    // 2.构建强引用
    sp spX(p);
    // 3.通过强引用,操作指针指向的对象
    spX->test();
}   // 作用域结束,调用强引用对象的析构函数,释放掉 p 指针指向的内存

默认情况下,弱引用用于记录一个指针值,不能通过弱引用来访问弱引用内部指针指向的对象,也就是说不能通过弱引用来调用其内部指针指向的对象的成员函数或访问其成员变量。要想访问弱引用内部指针所指向的对象,需首先将弱引用升级为强指针(通过 wp 类所提供的 promote() 方法),弱引用内部指针所指向的对象可能在其它地方被销毁了,如果该对象已经被销毁,wp 的 promote() 方法将返回空指针,这样就能避免出现地址访问错误的情况。其使用方法如下:

{   // 作用域开始
    // 1.被管理的指针
    X* p = new X();
    // 2.构建一个弱引用
    wp wpX(p);
    // 3. 不能直接通过弱引用使用对象,升级为强指针再使用
    sp spX2 = wpx.promote()
    if(spX2 != NULL)  // spX2 与 NULL 不是同一类型, 这里重载了 !=,实际比较的是 spX2 内部指针与 NULL 是否相等 
    {
        spX2->test();
    }
}

以上是 Android 平台智能指针的传统用法,Android12 中更新了一种全新的使用方法:

// frameworks/native/libs/binder/ProcessState.cpp

sp ProcessState::init(const char *driver, bool requireDefault)
{
    //......
    static sp gProcess;
    //......
    // 在 make 内部会使用参数 driver,new 一个 ProcessState 对象
    // 构建一个 sp 对象,其内部指针 m_ptr 执行刚 new 的 ProcessState 对象
    gProcess = sp::make(driver);
    //......
    return gProcess;
}

2.智能指针的原理

在 C++ 中,对象可以分为栈对象与堆对象:

void test() {
    //栈对象,test 函数执行结束后,会调用对象的析构函数
    Sheep sheep;
    Sheep* pSheep = new Sheep();//堆对象,需要手动释放
    delete pSheep; //释放pSheep指向的对象
    pSheep = 0;//将pSheep指向NULL,防止造成野指针}
}

当栈对象所在的作用域结束时,栈对象的析构函数会被执行,智能指针就是利用了这一点来实现内存自动释放功能的:

  • 引用对象是一个栈对象
  • 一个引用对象对应一个指针,引用对象内部保存有指针的值与一个引用计数值
  • 当增加指针的引用时,引用计数值加 1
  • 当引用对象所在作用域执行完后,会执行到引用对象的析构函数,在析构函数中,引用计数减 1,执行完减 1 操作后,如果引用计数值等于 0,那么释放引用对象内部指针指向的内存

以上就是智能指针的基本原理,接下来我们再来看看源码是如何实现的:

2.1 强引用源码分析

在上一节的示例代码中,我们定义了一个 X 类,X 类继承自 RefBase,当执行到 X* p = new X() 代码时,会执行 RefBase 的构造函数:

// system/core/libutils/RefBase.cpp
RefBase::RefBase()
    : mRefs(new weakref_impl(this))
{
}

//上面代码会执行到 weakref_impl 构造函数
explicit weakref_impl(RefBase* base)
        : mStrong(INITIAL_STRONG_VALUE)
        , mWeak(0)
        , mBase(base)
        , mFlags(0)
    {
    }

RefBase 构造函数会先 new 一个 weakref_impl 对象出来然后赋值给 mRefs 成员。weakref_impl 内部保存了智能指针需要的重要数据:

  • mStrong:强引用计数,当这个值为 0 时,需要回收内部指针指向的内存,初始值为 INITIAL_STRONG_VALUE 即 0x1000000
  • mWeak:弱引用计数,初始值为 0
  • mBase:RefBase* const 类型指针,指向需要管理的对象,也就是 RefBase 构造函数中传入的 this,当前对象指针
  • mFlags:一个标记值,标识是强引用控制还是弱引用控制,默认是 0 ,表示强引用控制,其可选值如下:
    // system/core/libutils/include/utils/RefBase.h
    enum {
        //强引用控制
        OBJECT_LIFETIME_STRONG  = 0x0000,
        //弱引用控制
        OBJECT_LIFETIME_WEAK    = 0x0001,
        //OBJECT_LIFETIME_MASK 是一个掩码值,后面会使用 flag&OBJECT_LIFETIME_MASK 的形式来获取 flag 的最低位数值
        OBJECT_LIFETIME_MASK    = 0x0001
    };
    
    // system/core/libutils/RefBase.cpp
    // mFlags 的值通常在目标对象(示例中的 X 类)的构造函数中通过 extendObjectLifetime 函数修改
    void RefBase::extendObjectLifetime(int32_t mode)
    {
        mRefs->mFlags.fetch_or(mode, std::memory_order_relaxed);
    }
    

当上一节的示例代码执行到 sp spX(p); 时,就会调用到 sp 的构造函数:

// system/core/libutils/include/utils/StrongPointer.h
template
sp::sp(T* other)
        : m_ptr(other) { // m_ptr 是一个指针,指向需要管理的对象
    if (other) {
        check_not_on_stack(other); // 确保 other 是一个堆对象
        other->incStrong(this);    // 增加强引用计数
    }
}

接着我们来看一看 incStrong 的具体实现:

// system/core/libutils/RefBase.cpp
void RefBase::incStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;  //拿到内部数据对象
    refs->incWeak(id); //增加弱引用计数
 
    refs->addStrongRef(id); // 由 DEBUG_REFS 控制,release 版本什么也不做
    //强引用计数加 1
    const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed);
    ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs);
#if PRINT_REFS
    ALOGD("incStrong of %p from %p: cnt=%dn", this, id, c);
#endif
    if (c != INITIAL_STRONG_VALUE)  { //判断是否为第一次引用
        return;
    }

    //第一次引用把初始化值减掉
    int32_t old __unused = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE, std::memory_order_relaxed);
    // A decStrong() must still happen after us.
    ALOG_ASSERT(old > INITIAL_STRONG_VALUE, "0x%x too small", old);
    //给被管理对象的回调接口,第一次引用时回调
    refs->mBase->onFirstRef();
}

// system/core/libutils/RefBase.cpp
void RefBase::weakref_type::incWeak(const void* id)
{
    weakref_impl* const impl = static_cast(this);
    impl->addWeakRef(id); // 由 DEBUG_REFS 控制,release 版本什么也不做
    //弱引用计数加 1
    const int32_t c __unused = impl->mWeak.fetch_add(1,
            std::memory_order_relaxed);
    ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
}

当强引用所在作用域执行完后,就会执行强引用对象的析构函数了:

这里分为两种情况:

  • 强引用控制模式 OBJECT_LIFETIME_STRONG
  • 弱引用控制模式 OBJECT_LIFETIME_WEAK

我们先分析强引用控制模式 OBJECT_LIFETIME_STRONG 下的 sp 析构流程:

// system/core/libutils/include/utils/StrongPointer.h
template
sp::~sp() {
    if (m_ptr)
        m_ptr->decStrong(this); //执行被管理对象的 decStrong 函数
}

// system/core/libutils/RefBase.cpp
void RefBase::decStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->removeStrongRef(id); // 由 DEBUG_REFS 控制,release 版本什么也不做
    // 强引用计数减 1
    // 注意返回值是执行减 1 之前的值
    const int32_t c = refs->mStrong.fetch_sub(1, std::memory_order_release);
#if PRINT_REFS
    ALOGD("decStrong of %p from %p: cnt=%dn", this, id, c);
#endif
    LOG_ALWAYS_FATAL_IF(BAD_STRONG(c), "decStrong() called on %p too many times",
            refs);
    if (c == 1) { // mStrong 等于 0 了,要执行内存回收操作了
        std::atomic_thread_fence(std::memory_order_acquire);
        //给被管理对象的回调接口
        refs->mBase->onLastStrongRef(id);
        int32_t flags = refs->mFlags.load(std::memory_order_relaxed);
        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) { //OBJECT_LIFETIME_STRONG 模式下
            delete this; //回收目标对象内存
        }
    }
    refs->decWeak(id); //弱引用减 1,回收内部对象 weakref_impl
}

// 这里 delete 了被管理对象,会执行到被管理对象的析构函数
RefBase::~RefBase()
{
    int32_t flags = mRefs->mFlags.load(std::memory_order_relaxed);
    
    //走 else 分支
    if ((flags & OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) { //如果是弱引用控制进入 if
        if (mRefs->mWeak.load(std::memory_order_relaxed) == 0) { //弱引用计数为 0
            delete mRefs; //清理内部指针
        }
    } else if (mRefs->mStrong.load(std::memory_order_relaxed) == INITIAL_STRONG_VALUE) {
     //打印一些信息,不用管
     //......
    }

    //内部 mRefs 指针赋值为 nullptr,防止野指针
    const_cast(mRefs) = nullptr;
}

//接着执行 decWeak
void RefBase::weakref_type::decWeak(const void* id)
{
    weakref_impl* const impl = static_cast(this);
    impl->removeWeakRef(id);
    // 弱引用计数减 1
    // 注意返回值是执行减 1 之前的值
    const int32_t c = impl->mWeak.fetch_sub(1, std::memory_order_release);
    LOG_ALWAYS_FATAL_IF(BAD_WEAK(c), "decWeak called on %p too many times",
            this);
    if (c != 1) return; //mWeak == 0 了往下执行
    atomic_thread_fence(std::memory_order_acquire);

    int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
    if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) { //强引用控制
       
        if (impl->mStrong.load(std::memory_order_relaxed) //sp 构造函数中对 mStrong 赋值了,走 else
                == INITIAL_STRONG_VALUE) {
           
            ALOGW("RefBase: Object at %p lost last weak reference "
                    "before it had a strong reference", impl->mBase);
        } else {
            delete impl; // 回收 RefBase 的内部对象 weakref_impl
        }
    } else { //弱引用控制
        impl->mBase->onLastWeakRef(id);
        delete impl->mBase;
    }
}

总结一下,在强引用控制模式下:

  • 被管理的对象必须继承自 RefBase
  • RefBase 的成员 weakref_impl* const mRefs 内部记录了强引用计数值与弱引用计数值
  • 当执行 sp 的构造函数时(即完成一次对指针的引用),强引用计数值加 1,弱引用计数值加 1
  • 当 sp 所在作用域指针完毕,执行 sp 的析构函数
    • 强引用减 1,减 1 后强引用计数值为 0,则回收 sp 内部指针 m_ptr 指向的内存(即 sp 管理的对象)
    • 弱引用减 1,减 1 后弱引用计数值为 0,则回收 RefBase 的成员 weakref_impl* const mRefs 指向的内存

可以看出,在强引用控制下,内存的回收与否是有强引用与弱引用共同决定的。

接着我们分析弱引用控制模式 OBJECT_LIFETIME_WEAK 下的 sp 析构流程:

// system/core/libutils/include/utils/StrongPointer.h
template
sp::~sp() {
    if (m_ptr)
        m_ptr->decStrong(this); //执行被管理对象的 decStrong 函数
}

// system/core/libutils/RefBase.cpp
void RefBase::decStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->removeStrongRef(id); // 由 DEBUG_REFS 控制,release 版本什么也不做
    // 强引用计数减 1
    // 注意返回值是执行减 1 之前的值
    const int32_t c = refs->mStrong.fetch_sub(1, std::memory_order_release);
#if PRINT_REFS
    ALOGD("decStrong of %p from %p: cnt=%dn", this, id, c);
#endif
    LOG_ALWAYS_FATAL_IF(BAD_STRONG(c), "decStrong() called on %p too many times",
            refs);
    if (c == 1) { // mStrong 等于 0 了,要执行内存回收操作了
        std::atomic_thread_fence(std::memory_order_acquire);
        //给被管理对象的回调接口
        refs->mBase->onLastStrongRef(id);
        int32_t flags = refs->mFlags.load(std::memory_order_relaxed);
        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) { //弱引用模式下不进入 if
            delete this; 
        }
    }
    refs->decWeak(id); //弱引用减 1,
}


//接着执行 decWeak
void RefBase::weakref_type::decWeak(const void* id)
{
    weakref_impl* const impl = static_cast(this);
    impl->removeWeakRef(id);
    // 弱引用计数减 1
    // 注意返回值是执行减 1 之前的值
    const int32_t c = impl->mWeak.fetch_sub(1, std::memory_order_release);
    LOG_ALWAYS_FATAL_IF(BAD_WEAK(c), "decWeak called on %p too many times",
            this);
    if (c != 1) return; //mWeak == 0 了往下执行
    atomic_thread_fence(std::memory_order_acquire);

    int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
    if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) { 
       
        if (impl->mStrong.load(std::memory_order_relaxed) else
                == INITIAL_STRONG_VALUE) {
           
            ALOGW("RefBase: Object at %p lost last weak reference "
                    "before it had a strong reference", impl->mBase);
        } else {
            delete impl; // 回收 RefBase 的内部对象 weakref_impl
        }
    } else { //弱引用控制,走 else 分支
        impl->mBase->onLastWeakRef(id);
        delete impl->mBase;  //delete 被管理对象
    }
}

// 这里 delete 了被管理对象,会执行到被管理对象的析构函数
RefBase::~RefBase()
{
    int32_t flags = mRefs->mFlags.load(std::memory_order_relaxed);
    
   
    if ((flags & OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) { //如果是弱引用控制进入 if
        if (mRefs->mWeak.load(std::memory_order_relaxed) == 0) { //弱引用计数为 0
            delete mRefs; //清理内部管理对象指针
        }
    } else if (mRefs->mStrong.load(std::memory_order_relaxed) == INITIAL_STRONG_VALUE) {
    }

    //内部 mRefs 指针赋值为 nullptr,防止野指针
    const_cast(mRefs) = nullptr;
}

弱引用控制模式 OBJECT_LIFETIME_WEAK 下的两个差异点:

  • 是否回收对象又弱引用计数 mWeak 决定
  • 目标对象和内部管理对象的回收时间点不同于强引用控制

2.2 弱引用源码分析

当我们上一节的示例程序执行到 wp wpX(p); 时,就会执行到指针 wp 的构造函数:

// system/core/include/utils/RefBase.h
template
wp::wp(T* other)
    : m_ptr(other) //保存指针到 m_ptr
{
    m_refs = other ? m_refs = other->createWeak(this) : nullptr; //增加弱引用计数,wp 内部的 m_refs 指向 createWeak 的返回值(即 RefBase 内部的 mRefs)
}

构造函数内部完成了三件事:

  • 保存指针到 m_ptr
  • 调用 createWeak 增加弱引用计数
  • 将 wp 内部的 m_refs 指向 createWeak 的返回值(即 RefBase 内部的 mRefs)

接着我们来看看 createWeak 源码的实现:

// system/core/libutils/RefBase.cpp
RefBase::weakref_type* RefBase::createWeak(const void* id) const
{
    mRefs->incWeak(id); //调用 weakref_impl 的 incWeak
    return mRefs;
}

// system/core/libutils/RefBase.cpp
void RefBase::weakref_type::incWeak(const void* id)
{
    weakref_impl* const impl = static_cast(this);
    impl->addWeakRef(id); // 由 DEBUG_REFS 控制,release 版本什么也不做
    //弱引用计数加 1
    const int32_t c __unused = impl->mWeak.fetch_add(1,
            std::memory_order_relaxed);
    ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
}

以上代码主要操作就是给弱引用计数加 1

接下来执行到弱引用转强引用的代码:

sp spX2 = wpx.promote()

template
sp wp::promote() const
{
    sp result;
    if (m_ptr && m_refs->attemptIncStrong(&result)) {
        result.set_pointer(m_ptr);
    }
    return result;
}

//处理了多种情况,总结一下就是弱引用加1 强引用加1
bool RefBase::weakref_type::attemptIncStrong(const void* id)
{
    incWeak(id); //弱引用加 1,在示例中,弱引用计数已经为 2

    //拿到内部管理对象
    weakref_impl* const impl = static_cast(this);
    //拿到强引用计数值
    int32_t curCount = impl->mStrong.load(std::memory_order_relaxed);

    ALOG_ASSERT(curCount >= 0,
            "attemptIncStrong called on %p after underflow", this);

   
    while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) {
        //强引用计数加 1
        if (impl->mStrong.compare_exchange_weak(curCount, curCount+1,
                std::memory_order_relaxed)) {
            break;
        }
    }


    if (curCount mFlags.load(std::memory_order_relaxed);

        if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) { //强引用计数控制
            if (curCount  0) {
                //强引用计数加 1
                if (impl->mStrong.compare_exchange_weak(curCount, curCount+1,
                        std::memory_order_relaxed)) {
                    break;
                }
            }

            if (curCount mBase->onIncStrongAttempted(FIRST_INC_STRONG, id)) { //始终是 false
                decWeak(id);
                return false;
            }

            //强引用加 1
            curCount = impl->mStrong.fetch_add(1, std::memory_order_relaxed);
            if (curCount != 0 && curCount != INITIAL_STRONG_VALUE) {
                impl->mBase->onLastStrongRef(id);
            }
        }
    }

    impl->addStrongRef(id);

#if PRINT_REFS
    ALOGD("attemptIncStrong of %p from %p: cnt=%dn", this, id, curCount);
#endif
    // 减掉初始值
    if (curCount == INITIAL_STRONG_VALUE) {
        impl->mStrong.fetch_sub(INITIAL_STRONG_VALUE,
                std::memory_order_relaxed);
    }

    return true;
}


template
void sp::set_pointer(T* ptr) {
    m_ptr = ptr;
}

处理了多种情况,核心流程就是弱引用加 1 强引用加 1。

当弱引用所在作用域执行完后,就会执行弱引用对象的析构函数了:

// system/core/include/utils/RefBase.h
template
wp::~wp()
{
    if (m_ptr) m_refs->decWeak(this); //指针内部对象的 decWeak 函数
}

//接着执行 decWeak
void RefBase::weakref_type::decWeak(const void* id)
{
    weakref_impl* const impl = static_cast(this);
    impl->removeWeakRef(id);
    // 弱引用计数减 1
    // 注意返回值是执行减 1 之前的值
    const int32_t c = impl->mWeak.fetch_sub(1, std::memory_order_release);
    LOG_ALWAYS_FATAL_IF(BAD_WEAK(c), "decWeak called on %p too many times",
            this);
    if (c != 1) return; // mWeak == 0 往下执行
    atomic_thread_fence(std::memory_order_acquire);

    int32_t flags = impl->mFlags.load(std::memory_order_relaxed);
    if ((flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) { //强引用控制
       
        if (impl->mStrong.load(std::memory_order_relaxed)
                == INITIAL_STRONG_VALUE) { //没有强引用指向目标对象
           
            ALOGW("RefBase: Object at %p lost last weak reference "
                    "before it had a strong reference", impl->mBase);
        } else { 
            delete impl; // 回收 RefBase 的内部对象 weakref_impl
        }
    } else { //弱引用控制
        impl->mBase->onLastWeakRef(id);
        delete impl->mBase;
    }
}

析构函数中,弱引用计数减 1 后等于 0 则:

  • 强引用控制: 回收 RefBase 的内部对象 weakref_impl
  • 弱引用控制: 回收 impl->mBase,也就是管理的目标对象,进而执行到 RefBase 的析构函数

总结

智能指针的基本原理和使用是非常简单的,Android 平台智能指针的实现略显繁琐,主要原因是增加了弱引用支持以及对各类边界情况的处理。我们需要熟练掌握智能指针的使用,了解智能指针初始化和析构的主干流程,如果在实践中遇到相关问题,再深入源码,分析各类边界情况。

参考资料

  • Android智能指针RefBase、sp、wp解析
  • android系统核心机制 基础(01)智能指针wp & sp
  • Android基础--智能指针

关于

我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,主要研究方向是 Android Framework 与 Linux Kernel。

如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。

相关文章

服务器端口转发,带你了解服务器端口转发
服务器开放端口,服务器开放端口的步骤
产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
如何使用 WinGet 下载 Microsoft Store 应用
百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

发布评论