概述

在项目中我们常常用到weak指针,其能够确保在指向的目标开释后,weak指针主动置为nil,以避免溃散,因为在OC中向nil发送音讯是没有任何处理的。经过__weakproperty weak等方式,都能够将指针润饰为weak类型的。

weak的完结原理其实很简单,归纳来说便是,在内存中有一个名为weak_table_t哈希表weak_table_t中存储着App一切的weak目标及指针。当有目标被weak指针润饰时,会将被润饰的目标及指针增加到weak_table_t表中。当被weak指针的效果域消失时,weak指针会被毁掉,随后会从哈希表中查找对应的weak指针,并将指针置为nil

weak引证表

SideTables

weak的完结中许多地方都用到了SideTables函数,此函数内部直接回来了一个哈希表,哈希表名为SideTablesMap。此函数用来保存整个程序一切被weak指针指向的目标,和应用程序是一对一的。每个目标对应其间的一个元素,例如两个weak指针指向一个目标,则这个目标对应一个SideTable目标,这个目标中保存这两个weak指针。

依照比较新的runtime 779.1版别,关于SideTables的界说如下。SideTables本质上是一个静态函数,其内部是经过SideTablesMap完结的。

static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}

下面界说是一个嵌套模型,其间SideTablesMap是一个objc命名空间下的ExplicitInit类,其内部完结的上面的get函数。StripedMap也是一个模型,传入的SideTable便是大局的weak表地点的结构体。

static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;

ExplicitInit

ExplicitInit是一个模板类,传入的type便是上面界说的StripedMap。在get函数中有reinterpret_cast要害字,这个要害字类似于强制类型转换,改动类型但存储的数据不变。

template <typename Type>
class ExplicitInit {
    alignas(Type) uint8_t _storage[sizeof(Type)];
public:
    template <typename... Ts>
    void init(Ts &&... Args) {
        new (_storage) Type(std::forward<Ts>(Args)...);
    }
    Type &get() {
        return *reinterpret_cast<Type *>(_storage);
    }
};

SideTable

SideTable结构体是weak完结的中心,结构体中界说了引证计数表和弱引证表,弱引证运用weak_table字段。下面的slock自旋锁,两个表都会运用同一个锁。

struct SideTable {
    // 自旋锁,确保线程安全
    spinlock_t slock;
    // 引证计数表,在未敞开isa指针优化,或isa指针存储满了才会用
    RefcountMap refcnts;
    // 弱引证表
    weak_table_t weak_table;
};

weak_table_t

weak_table_t是弱引证表,一切的弱引证都会被存储在这个表中。在下面结构体变量的界说中,weak_entries是一个哈希表的结构,其间key是堆区内存地址,经过key能够获取weak_entries链表中对应的weak_entry_t,也便是指针数组。

struct weak_table_t {
    // 弱引证数组,用来存储weak_entry_t目标,是一个链表结构
    weak_entry_t *weak_entries;
    // 弱引证数组巨细,如果到阈值会主动扩容
    size_t    num_entries;
    // 进行哈希运算的mask,巨细是num_entries-1
    uintptr_t mask;
    // 最大抵触数,一般不会大于这个数
    uintptr_t max_hash_displacement;
};

weak_entry_t

weak_entry_t结构体内部的界说是一个union联合体,联合体中包含两个结构体,此结构体用来存储指针地址。在存储时会判别,如果同一个目标的weak指针数量少于4,则运用inline_referrers定长数组,不然运用referrers动态长度数组。

有趣的是,因为是union联合体,当weak指针数量大于4之后,不需求另外开辟空间,在当时空间直接覆盖inline_referrers的存储区域,运用referrers哈希表。

#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            // 弱引证该目标的,指针地址的哈希数组
            weak_referrer_t *referrers;
            // 是否运用动态长度数组
            uintptr_t        out_of_line_ness : 2;
            // referrers数组中的元素
            uintptr_t        num_refs : PTR_MINUS_2;
            // 参与哈希运算的值,是referrers数组分配的长度,会跟着动态扩容而改动
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

weak_referrer_t

无论是inline_referrers数组仍是referrers链表,都是一个weak_referrer_t的界说,在这个typedef中界说了一个指向objc_object的指针,isa指针都是被优化为objc_object的,所以这个指针指向这个被优化的isa。也能够简单理解为,weak_referrer_t本质上存储着被weak润饰的指针地址。

结合weak_entry_t的界说,能够知道referrers数组存储结构,便是直接将DisguisedPtr转换后的整型负数,存储在referrers数组中。

typedef DisguisedPtr<objc_object *> weak_referrer_t;

DisguisedPtr

DisguisedPtr是一个模板东西类,首要起到将指针和整型彼此转换的效果,意图是为了躲藏指针,起到一个伪装的效果。躲藏指针的效果在于,一方面是为了安全性,另一方面也避免leaks这些检测东西的误判。

template <typename T>
class DisguisedPtr {
    // 存储地址转换为整数的结果
    uintptr_t value;
    // 将地址转为整数,并取反
    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }
    // 将转换为负数的地址,先取反随后转换为地址
    static T* undisguise(uintptr_t val) {
        return (T*)-val;
    }
 public:
    // 构造函数
    DisguisedPtr() { }
    // 经过指针,初始化value
    DisguisedPtr(T* ptr) 
        : value(disguise(ptr)) { }
    DisguisedPtr(const DisguisedPtr<T>& ptr) 
        : value(ptr.value) { }
    // 运算符重载,将指针转换为DisguisedPtr的进程,直接由“&”取地址符来完结
    DisguisedPtr<T>& operator = (T* rhs) {
        value = disguise(rhs);
        return *this;
    }
    DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
        value = rhs.value;
        return *this;
    }
    operator T* () const {
        return undisguise(value);
    }
    // 运算符重载,将地址转为指针
    T* operator -> () const { 
        return undisguise(value);
    }
    T& operator * () const { 
        return *undisguise(value);
    }
    T& operator [] (size_t i) const {
        return undisguise(value)[i];
    }
};

创立weak指针

objc_initWeak

经过__weak等方式创立的指针,编译器会将其转换为objc_initWeak函数的调用,并将被指向目标及weak指针传进去。但需求留意的是,此函数并不是线程并发安全的,所以需求留意多线程的运用。

id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

在调用函数后会将haveNewnewObj等参数传入,供storeWeak调用。

storeWeak

无论是创立weak指针仍是毁掉weak指针,其内部完结都是经过storeWeak函数完结的。storeWeak函数是一个C++的模板函数,函数会传入五个参数。其间haveOldhaveNew是互斥的,haveNew则表明新创立的weak指针,haveOld则表明将已有的weak指针置为nil,或将指针重定向。

由于storeWeak函数中的代码量较大,所以只保留了中心代码,不重要的代码都删掉了,看的也清楚点。如果是经过objc_initWeak调用进来,下面template的三个bool参数界说,分别如下。

  • haveOld = false
  • haveNew = true
  • crashIfDeallocating = true
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    if (!haveNew) assert(newObj == nil);
    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
 retry:
    // 判别location是否现已指向weak目标,有的话先把旧目标取出来
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    // 将新传入的目标,对应的weak表取出来,如果这个目标之前被weak指针指向过,则回来有值
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    // 如果有旧目标,可是不是location所指向的,也便是当时传入的目标。则表明从retry履行到这里,可能被其他线程改过,所以需求从头履行retry
    if (haveOld  &&  *location != oldObj) {
        goto retry;
    }
    // 有新目标,则判别类是否初始化,没有初始化则先初始化,再从头履行retry
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
            previouslyInitializedClass = cls;
            goto retry;
        }
    }
    // 如果location指针指向过其他目标,则履行weak_unregister_no_lock,将weak表和旧目标解绑
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // 将传入的新目标,以及新目标的指针地址,增加到weak表中
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }
        *location = (id)newObj;
    }
    return (id)newObj;
}

storeWeak函数中会进行判别,如果是haveNew则表明创立一个weak指针,这时会判别被指向的目标是否第一次传入,如果是则会创立新的内存空间,如果是第二次被weak指向则取出之前现已创立的weak_table。当开释weak指针时也是如此,先取出SideTable目标再从weak_table中移除对应的weak指针。

随后会进入一个判别,会判别弱引证目标所属的类目标是否未履行+initialized办法,如果未履行则调用下面的代码初始化类目标。以确保在弱引证目标和+initialized办法之间,不会发生死锁。

最后会进入详细的完结中,这些完结代码都在objc-weak.mm中。移除weak引证或指针重定向会经过weak_unregister_no_lock函数完结,增加新的weak引证则会经过weak_register_no_lock函数完结。

weak_register_no_lock

weak_register_no_lock函数是增加weak引证的要害,在函数中会有是否能够进行weak操作的判别,如果不契合条件则return或抛出反常。需求留意的是,在NSObject.mm文件中供给了allowsWeakReference办法,能够经过此办法回来是否允许运用weak引证,在下面的代码中也会向此办法发送音讯来做确认。

weak_register_no_lock函数传入的四个参数如下。

  • weak_table,大局的weak哈希表
  • referent_idweak指针。
  • *referrer_idweak指针地址。
  • crashIfDeallocating,如果weak指向的地址正在开释中,是否crash
id weak_register_no_lock(weak_table_t *weak_table, id referent_id,
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    // 判别传入的目标是否为空,以及是否tagged pointer这些鸿沟判别
    if (!referent || referent->isTaggedPointer()) return referent_id;
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        // 有自界说的retain完结,履行自界说办法
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }
    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("error");
        } else {
            return nil;
        }
    }
    // 判别堆区内存是否有被weak引证过,如果有的话就加入到已有的weak_entry_t中
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        // 如果没有,则创立一个weak_entry_t,并刺进到大局的weak_table哈希表中
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }
    return referent_id;
}

在函数中经过referent获取到被weak引证的目标,经过referrer获取到指针目标,随后会将这两个变量包装成weak_entry_t结构体,并调用对应的函数增加到哈希表中。

履行增加操作的有两个要害函数,当目标第一次有weak指针指向时,会调用weak_entry_insertweak_entry_t刺进到哈希表中,后面的weak指向都会调用append_referrer函数向表中增加referrer

weak_entry_for_referent

weak_entry_for_referent函数用来查找referentweak指向的堆区地址,也便是objc_object目标的地址。随后经过hash_pointer函数找到referent地点的index,也便是哈希表的key。在while循环中遍历weak_entries表,从index开端查找referent地点的方位,一般很快就能找到,并回来。

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    ASSERT(referent);
    weak_entry_t *weak_entries = weak_table->weak_entries;
    if (!weak_entries) return nil;
    // 经过哈希算法获取到referent的index,也便是地点的下标
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    // 循环时从index地点的下标开端,这样查找速度会很快
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    return &weak_table->weak_entries[index];
}

append_referrer

此函数是对weak_entry_t进行刺进,向数组后面拼接指向指针的地址。内部会依据哈希表巨细,决定用动态数组仍是定长数组,并且会依据运用情况,对动态数组进行扩容。并最终刺进到weak_entry_t的结尾。

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    // 如果还未敞开动态长度数组,则进入这里
    if (! entry->out_of_line()) {
        // 先尝试定长数组,如果有空方位就刺进
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }
        // 如果定长数组存满了,就创立动态数组,并将定长数组的目标刺进进来
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }
    ASSERT(entry->out_of_line());
    // 判别weak_entry_t现已运用的巨细,是否超越了现已开辟的3/4,如果超越则进行动态扩容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    // 经过哈希算法查找当时的index
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 经过while循环,找到referrers数组的最新一个为空的方位
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        // 由于扩容是成倍扩容的,所以mask的值一定是,0x111, 0x1111, 0x11111
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    // 将new_referrer刺进到新的空方位,并将count加一
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

weak_entry_insert

weak_table_t中界说了weak_entries结构体变量,这是一个数组结构,弱引证目标都保存在这里。当刺进一个weak_entry_t时会遍历数组,并找到适宜的方位将其刺进。

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t *weak_entries = weak_table->weak_entries;
    assert(weak_entries != nil);
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;
    }
    weak_entries[index] = *new_entry;
    weak_table->num_entries++;
    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

指针重定向

有时候会把weak指针指向一个新的目标,这时候会涉及到指针重定向。新建weak指针会传入newObj并调用对应的函数,weak指针效果域毁掉会传入oldObj并调用对应的函数,而指针重定向则这两个参数都有值,haveNewhaveOld也都是有值的。

指针重定向会在一次storeWeak函数调用中处理,并按次序先调用weak_unregister_no_lock函数,先将weak指针从哈希表中移除,再调用weak_register_no_lock函数增加到新的SideTable中。

dealloc

rootDealloc

当目标开释时,会调用dealloc办法,并调用到rootDealloc来完结开释逻辑。在开释时会判别,如果没有弱引证等逻辑,能够直接调用free函数开释,不然调用object_dispose函数处理开释逻辑。

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;
    // 如果契合下面条件,则直接调用free函数开释内存
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

objc_destructInstance

object_dispose函数中调用的objc_destructInstance函数,在objc_destructInstance函数中,履行了一些开释和收尾的工作。而weak的开释操作,就在clearDeallocating中完结的。

void *objc_destructInstance(id obj)
{
    if (obj) {
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }
    return obj;
}

clearDeallocating

clearDeallocating函数中,会判别是否敞开指针优化,如果未敞开则履行sidetable的开释逻辑。如果敞开了指针优化,并且有weak指针,或引证计数的逻辑,则履行clearDeallocating_slow函数。

inline void
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        clearDeallocating_slow();
    }
    assert(!sidetable_present());
}

clearDeallocating_slow函数内部,实际上经过weak_clear_no_lock函数对weak指针开释逻辑进行的完结。

weak_clear_no_lock

weak最中心的功用,即目标开释时,将weak指针指向nil。这个逻辑就在weak_clear_no_lock函数中完结的。

void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;
    // 依据被开释的目标referent_id,从weak_table中找到weak_entry_t结构体
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        return;
    }
    weak_referrer_t *referrers;
    size_t count;
    // 判别用的是定长数组,仍是动态数组,并将数组赋值给referrers指针
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    // 遍历weak数组,并将指向weak堆区地址的指针,都置为nil
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    // 遍历完毕后,从weak_table哈希表中,将weak_entry_t移除
    weak_entry_remove(weak_table, entry);
}

开释优化

objc_destroyWeak

weak作为一个局部变量出现时,编译器会对weak进行优化。创立目标后,会经过objc_initWeak的方式将weak指针增加到哈希表中。在效果域完毕时,会经过objc_destroyWeakweak直接开释掉。

weak指针效果域消失时,体系会调用objc_destroyWeak函数来处理,并在函数内部调用storeWeak函数。随后storeWeak函数中会传入haveOld,表明是履行移除weak的操作。

void objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

weak_unregister_no_lock

weak_unregister_no_lock函数是在storeWeak函数中调用的,当weak指针第一次指向或从头指向时,都会调用storeWeak函数。如果从头指向,会调用weak_unregister_no_lock先将之前的移除去。

函数会经过referent获取到被weak引证的目标,经过referrer获取到指针目标。随后会经过weak_entry_for_referent函数查找被指向的目标是否有weak_entry_t,如果找到则进入if语句中。

if语句中会调用remove_referrer函数,将被weak指针从weak_entry_t中移除,并将weak指针置nil,这步便是最要害的一步了。随后会判别,如果被指向的目标,一切weak指针都没有了,则将目标从weak_table的哈希表中移除。

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    weak_entry_t *entry;
    if (!referent) return;
    // 从weak_table中获取weak_entry_t目标,如果有的话则进入if语句
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 从weak_entry_t中删去referrer引证
        remove_referrer(entry, referrer);
        // 判别删去后,weak_entry_t是否还有弱引证指针指向这块内存地址
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }
        // 如果为空,则将weak_entry_t从大局weak_table哈希表中删去
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }
}

remove_referrer

这是weak完结的要害函数,和向weak_entry_t中增加一样,只是这个进程是逆向的。会先遍历inline_referrers数组中有没有指针目标,如果有则将指针置为nil,不然就查找referrers数组。

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (!entry->out_of_line()) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
    }
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    entry->referrers[index] = nil;
    entry->num_refs--;
}